Compare commits

...

501 Commits

Author SHA1 Message Date
97836cf09f Merge "Always output upstream if specified" 2015-07-13 16:36:28 +00:00
80e3a37ab5 Merge changes Iaefcbe14,I697a0f64,I19bfe9fe,I06e942c4
* changes:
  forall: use smart sync override manifest if it exists
  sync: Remove smart sync override manifest when not in smart sync mode
  forall: Don't try to get lrev of projects in mirror workspace
  sync: Improve error message when writing smart sync manifest fails
2015-07-11 14:01:16 +00:00
bb4a1b5274 Merge "Improve error message when syncing a project with invalid groups." 2015-07-10 22:00:47 +00:00
551dfecea9 Always output upstream if specified
Previously, in running the `manifest` command, we wouldn't output the
upstream if the default upstream would include the pinned sha1.
However, now that fetching refs/heads/* doesn't guarantee that we will
have the sha1, we need to always output the specified upstream branch.

Change-Id: Ib8b409a8ecd439397b38ee9649c530407797f841
2015-07-10 14:59:10 -07:00
6944cdb8d1 forall: use smart sync override manifest if it exists
If a workspace is synced with the -s or -t option, the included projects
may be different to those in the original manifest. However, when using
the forall command, the list of the projects from the original manifest
is used.

If the smart sync manifest file exists, use it to override the original
manifest.

Change-Id: Iaefcbe148d2158ac046f158d98bbd8b5a5378ce7
2015-07-06 16:18:06 +09:00
59b417493e sync: Remove smart sync override manifest when not in smart sync mode
When syncing with the -s or -t option, a smart_sync_override.xml file
is created. This file is left in the file system when syncing again
without the -s or -t option.

Remove the smart sync override manifest, if it exists, when not using
the -s or -t option.

Change-Id: I697a0f6405205ba5f84a4d470becf7cd23c07b4b
2015-07-06 16:18:06 +09:00
30d13eea86 forall: Don't try to get lrev of projects in mirror workspace
git rev-parse fails for projects that don't have an explicit revision
specified, and don't have a branch of the same name as the default
revision. This can be the case in a workspace synced with the smart
sync (-s) or smart tag (-t) option.

Change-Id: I19bfe9fe7396170379415d85f10f6440dc6ea08f
2015-07-06 16:18:06 +09:00
727cc3e324 sync: Improve error message when writing smart sync manifest fails
The error message only states that writing the manifest failed.

Include the exception message, so it's easier to track down the reason
that the write failed.

Change-Id: I06e942c48a19521ba45292199519dd0a8bdb1de7
2015-07-06 16:18:06 +09:00
c5ceeb1625 Merge "Fix 'repo cherry-pick' to avoid hanging on commit-msg update." 2015-06-25 14:53:46 +00:00
db75704bfc Fix 'repo cherry-pick' to avoid hanging on commit-msg update.
After performing the actual cherry-pick operation, the code
in cherry_pick.py opens a pipe to 'git commit -F' to rewrite the commit
message, emits the fixed-up commit msg to the pipe, then waits
for 'git commit' to complete. The child 'git' process winds up
hanging while reading from the pipe, however, since the parent
process still has it open. To fix the hang, change the parent process
to close its end of the pipe after it has emitted the message.

Change-Id: I5929371e69a5b076f09009d00d40a2c72ac8ac33
2015-06-22 08:00:20 -04:00
87ea5913f2 Improve error message when syncing a project with invalid groups.
Change-Id: Iaf5c2a0f00667dc09bcf455cfe2f39bfbaa2bfc0
2015-06-19 15:55:15 -07:00
185307d1dd Merge "Teach _LinkFile._Link to handle globs." 2015-06-09 00:14:13 +00:00
c116f94261 forall: setenv, only encode val if encode exists
Change-Id: I655e3043d0118c4e929897d3a51e5e013e5758dc
2015-06-04 00:34:19 +00:00
7993f3cdda init: don't call urllib.parse
it's actually urllib.parse.urlparse

Change-Id: Ie3532e54625e887c8682d92b932ea21a629e8d60
2015-06-04 00:33:33 +00:00
b1d1fd778d git_config: fix _SaveJson typo
Change-Id: I35ca2b3733e6d1508669f9a6690c6645c582912e
2015-06-04 00:22:23 +00:00
be4456cf24 error: fix typos
Change-Id: I09c47024ef54c360ea3c15c5d4f169e13444e412
2015-06-04 00:21:16 +00:00
cf738ed4a1 git_command: only decode when needed
strings no longer need decoding, since unicode is str

Change-Id: I9516d298fee7ddc058452394b7759327fe3aa7a8
2015-06-03 16:50:39 +01:00
6cfc68e1e6 decode the buffer before appending
output from a process is in bytes in python3. we need
to decode it.

in Python3, bytes don't have an encode attribute. use this
to identify it.

Change-Id: I152f2ec34614131027db680ead98b53f9b321ed5
2015-06-03 16:39:32 +01:00
4c426ef1d4 Teach _LinkFile._Link to handle globs.
This allows a project to use globs in the linkfile src attribute. When
a glob is used in the src the dest field must be a directory. Then
_LinkFile._Link(self) calls will create symbolic links in the dest
directory to all of the entries in the src as defined by the glob
specification.

Below all of the entries in master-configs/ will have symbolic links
in <root dir>/configs directory:

  <project name="helloworld.git" path="apps/helloworld">
      <linkfile src="master-configs/*" dest="configs"/>
  </project>

Change-Id: Idfed8fa47c83d2ca6e2b8e867731b8e2f9e2eb47
2015-06-03 08:05:17 -07:00
472ce9f5fa Merge changes I32da12c2,Ie4a65b3e
* changes:
  Skip sleep and retry if git remote update exits with a signal
  Catch exceptions in project list generator
2015-06-02 00:14:43 +00:00
0184dcc510 Make linkfile symlinks relative
The source (target) of the symlink is specified relative to a project
within a tree, and the destination is specified relative to the top
of the tree, so it should always be possible to create a relative symlink
to the target file.  Relative symlinks will allow moving an entire tree
without breaking the symlink, and copying a tree (with -p) without leaving
a symlink to the old tree.

Change-Id: I16492a8b59a137d2abe43ca78e3b212e2c835599
2015-06-01 01:24:38 +00:00
c4b301f988 Skip sleep and retry if git remote update exits with a signal
Pressing ctrl-c during repo sync often hangs for 30 to 45 seconds
due to the time.sleep and retry in _RemoteFetch.  If git exits with
a signal, for example -2 for SIGINT triggered by ctrl-c, skip the
sleep and retry.

Change-Id: I32da12c2dcc96d9cc0b12a066e824b12ebfb52a0
2015-05-13 18:11:34 +00:00
31a7be561e Catch exceptions in project list generator
If the generator that produces per-project worker arguments raises an
exception it triggers python bug http://bugs.python.org/issue8296.
Rewrite the generator expression as a generator function, and catch
Exceptions and KeyboardInterrupts to end the iteration.

Also add a pool worker initializer to disable SIGINT to prevent
KeyboardInterrupts inside multiprocessing.Pool in the worker threads
causing the same problem.

Fixes easy-to-reproduce hangs when hitting ctrl-c during
repo forall -c echo

Change-Id: Ie4a65b3e1e07a64ed6bb6ff20f3912c4326718ca
2015-05-13 11:09:38 -07:00
35de228f33 Merge "Don't attempt to create "fully qualified names" for SHA1s" 2015-05-11 09:20:54 +00:00
ace097c36e Merge "Add option on sync to avoid fetching from remotes for existing sha1" 2015-05-01 07:51:52 +00:00
b155354034 Add option on sync to avoid fetching from remotes for existing sha1
In 2fb6466f79 an optimisation was
added to avoid fetching from remotes if the project is fixed to
a revision and the revision is already available locally.

This causes problems for users who expect all objects to be
fetched by default.

Change the logic so that the optimized behaviour is only enabled if
an option is explicitly given to repo sync.

Change-Id: I3b2794ddd8e0071b1787e166463cd8347ca9e24f
2015-04-30 14:29:02 +00:00
382582728e Don't attempt to create "fully qualified names" for SHA1s
Doing so breaks "repo init -b <SHA1>".

Change-Id: Ic071a1b099a9125db22ea446d7e92e7854d69b37
2015-04-30 14:54:47 +02:00
b4d43b9f66 Add --prune option to fetch when syncing a mirror repo
When syncing a mirror repo, add the --prune option to the fetch
command to force removal of stale refs from the mirror.

Change-Id: I4b43b2a5c86b9915627887c16f6569066f3ab978
2015-04-30 10:32:37 +09:00
4ccad7554b Fix substitution err for schemeless manifest urls
Previously, we used a regex that would only remove a phony string from
a url if it existed, but we recently replaced that with a slice.  This
change goes back to the previous behavior.

Change-Id: I8baf527be01c4b49d45b903b31a1cd6315563d5b
2015-04-29 10:45:37 -07:00
403b64edf4 Don't append branch to fetch spec when syncing to a mirror
Appending the branch to the fetch spec causes sync of a mirror to
fail for projects that don't have an explicit revision specified,
and don't have a branch of the same name as the default revision.

For example, a manifest defining a default revision:

 <default revision="master">

having a project without an explicit revision:

 <project name="path/to/project">

and not having a branch named "master", will cause repo sync to
fail for that project with the error:

 Couldn't find remote ref refs/heads/master

Modify the logic to not append the branch onto the fetch spec when
syncing to a mirror.

Change-Id: I5c4457bd125519abf27abe682dea62ad708978c9
2015-04-27 10:56:27 +09:00
a38769cda8 Merge "forall: use a generator to map the Pool" 2015-04-08 17:59:58 +00:00
44859d0267 Merge "status: lose dependence on StringIO" 2015-04-08 17:58:35 +00:00
6ad6dbefe7 forall: use a generator to map the Pool
Before, a list was generated, which is why there was a massive delay.

Using a generator will allow processes to start straight away

Change-Id: Ia325b0b340cc328c08c9bcc92a6709bbdaf6a664
2015-04-08 13:22:34 +01:00
33fe4e99f9 Remove deprecated include-ids setting from pylint config
Change-Id: Ie5ab21e434d24ff862bb5e0c263761370d71f56f
2015-04-07 11:10:17 +09:00
4214585073 Merge "Pylint and PEP8 fixes for color.py" 2015-04-07 02:06:49 +00:00
b51f07cd06 status: lose dependence on StringIO
buflist was being used, which isn't available in Python 3.

`Execute` was using StringIO to capture the output of `PrintWorkTreeStatus`,
only to redirect it straight to stdout.
Instead, just let `PrintWorkTreeStatus` do it's own thing directly to stdout.

for handling `_FindOrphans`, we swap StringIO for a list. Nothing was done
that needed a a file like object.

Change-Id: Ibdaae137904de66a5ffb590d84203ef0fe782d8b
2015-04-04 21:21:49 +01:00
04f2f0e186 Maintain fully qualified tracking branches
When running repo branch, the git merge line (in many circumstances)
is set to the revision of the project specified in the manifest.  If
this is a branch name that is not fully-qualified, we will end up with
something like "merge = master" instead of "merge = refs/heads/master".
This change examines the revision if we are going to use that and
changes branch short names to fully qualified branch names.

Change-Id: Ie1be94fb8d45df8eeac44a47f729a3819a05fa81
2015-04-01 17:43:36 +00:00
cb07ba7e3d Resolve fetch urls more efficiently
Instead of using regex, append the netloc and relative
scheme lists with the custom scheme.
The schemes will only be appended when needed, instead
of passing X amount of regex replaces.

see http://bugs.python.org/issue18828 for more details.

Change-Id: I10d26d5ddc32e7ed04c5a412bdd6e13ec59eb70f
2015-03-31 20:12:44 +00:00
23ff7df6a7 use the max depth instead of unshallow
This allows the use of older versions of git

Change-Id: I88ea685066603af19896a791829355ddbfa91ffe
2015-03-30 21:54:26 +00:00
cc1b1a703d Revert "Change the min git version from 1.7.2 to 1.8.2"
This reverts commit 52b99aa91d.

Change-Id: I01d93704c92f7af1ca2b36dbc9509ee1290e2d3c
2015-03-30 21:53:25 +00:00
bdf7ed2301 Pylint and PEP8 fixes for color.py
Change-Id: I1a676e25957a7b5dd800d2585a2ec7fe75295668
2015-03-28 21:12:27 +00:00
9c76f67f13 Always capture output for GitCommand
Switch the GitCommand program to always capture the output for stdout
and stderr.  And by default print the output while running.

The options capture_stdout and capture_stderr have effectively become
options to supress the printing of stdout and stderr.

Update the 'git fetch' to use '--progress' so that the progress messages
will be displayed.  git checks if the output location isatty() and if it
is not a TTY it will by default not print the progress messages.

Change-Id: Ifdae138e008f80a59195f9f43c911a1a5210ec60
2015-03-26 11:43:55 -07:00
52b99aa91d Change the min git version from 1.7.2 to 1.8.2
This is needed for the --unshallow option of git fetch.

Change-Id: Ifdc5cec6130315c643924328fea425f1b94cb04a
2015-03-18 21:43:39 +00:00
9371979628 Revert "Implementation of manifest defined githooks"
This reverts commit 38e4387f8e.

A "repo init" followed by "repo sync" is meant to be as safe as
"git clone".  In particular it should not run arbitrary code provided
by the manifest owner.

It would still be nice to have support for manifest-defined git hooks
--- they'd just need a prompt like the upload RepoHook has.  Hopefully
a later change can bring them back.

Change-Id: I5ecd90fb5c2ed64f103d856d1ffcba38a47b062d
Signed-off-by: Jonathan Nieder <jrn@google.com>
2015-03-17 11:29:58 -07:00
2086004261 Merge "Don't exit with error on HTTP 401 when downloading clone bundle" 2015-03-11 17:25:45 +00:00
2338788050 Don't exit with error on HTTP 401 when downloading clone bundle
If the server returns HTTP 401 (unauthorized) when attempting to
download clone bundle files, ignore it and continue, rather than
exiting with a fatal error.

Change-Id: I2c7ee03e149c354c7e4ad6ea1ebf266534778fe1
2015-03-11 07:43:40 +00:00
0402cd882a Add space between project path and branch in repo status.
Currently, paths longer than 39 chars have no space after them so it looks
like this:

project path/branch master

Change-Id: I4c1bb13648ac099ade8a8d4ebafa04131571f842
2015-03-11 07:42:17 +00:00
936183a492 git_config: add support for remote '.'
As a fix for issue #149, this patch add support for the remote '.'
(local).

As an alias for the local repository, remote '.' is lacking a fetch =
config in .git/config.

Without such refspec, repo info --overview is not able to process a
local tracking branch.

v2: Check for name == '.' before checking if merge starts with refs/,
    since the case where it's not is invalid.

Signed-off-by: Yann Droneaud <ydroneaud@opteya.com>
Signed-off-by: Filipe Brandenburger <filbranden@google.com>

Change-Id: I8c8fd8602cd68baecb530301ae41d37d751ec85d
2015-03-06 13:23:27 -08:00
85e8267031 Merge "Implementation of manifest defined githooks" 2015-03-05 20:52:30 +00:00
e30f46b957 Print stderr output from git command for RemoteFetch
The stderr output generated by git during a RemoteFetch was not being
printed.  This information is useful so print it.

Change-Id: I6e6ce12c4a57e5ca2359f76ce14f2fcbbc37a5ef
2015-02-25 14:29:28 -08:00
e4978cfbe3 Ensure the repo project is never fetched with partial depth
If the repo project is synced with partial depth, then the tags
won't be fetched and users will be told the newest sha1 in the
stable branch isn't signed.

Change-Id: I107df97b4836b928c76aa33a700fa35d1705ae09
2015-02-10 14:44:05 -08:00
126e298214 Handle case where 'git remote prune' needs to be run
Handle the case when this error occurs:
    error: some local refs could not be updated; try running
     'git remote prune origin' to remove any old, conflicting branches

This is usually caused by a reference getting changed from a file to a
directory.

For example:
  Initially someone creates a branch 'foo' and it is stored as:
    .git/refs/remotes/origin/foo

  Then later on it is decided to change the layout structure where 'foo'
  is a directory with branches below it:
    .git/refs/remotes/origin/foo/master

  The problem occurs when someone still has
  '.git/refs/remotes/origin/foo' on their system and does a repo sync.
  When this occurs the error message for needing to do a
  'git remote prune origin' occurs.

Now when doing a 'git fetch' if the error message from git says that a
'git remote prune' is needed, it will do the prune and then retry the
fetch.

Change-Id: I4c6f5aa6bd932f0ef7a39134400bedd52e82f633
Signed-off-by: John L. Villalovos <john.l.villalovos@intel.com>
2015-02-03 13:49:51 -08:00
38e4387f8e Implementation of manifest defined githooks
When working within a team or corporation it is often
useful/required to use predefined git templates. This
change teaches repo to use a per-remote git hook template
structure.

The implementation is done as a continuation of the
existing projecthook functionality. The terminology is
therefore defined as projecthooks.

The downloaded projecthooks are stored in the .repo
directory as a metaproject separating them from the users
project forest.

The projecthooks are downloaded and set up when doing a
repo init and updated for each new repo init.

When downloading a mirror the projecthooks gits are
not added to the bare forest since the intention is to
ensure that the latest are used (allows for company policy
enforcement).

The projecthooks are defined in the manifest file in the
remote element as a subnode, the name refers to the
project name on the server referred to in the remote.
<remote name="myremote ..>
   <projecthook name="myprojecthookgit" revision="myrevision"/>
</remote>

The hooks found in the projecthook revision supersede
the stock hooks found in repo. This removes the need for
updating the projecthook gits for repo stock hook changes.

Change-Id: I6796b7b0342c1f83c35f4b3e46782581b069a561
Signed-off-by: Patrik Ryd <patrik.ryd@stericsson.com>
Signed-off-by: Ian Kumlien <ian.kumlien@gmail.com>
2015-02-03 16:01:15 +09:00
24245e0094 Merge "Add missing documentation of --current-branch option on sync command" 2015-01-31 12:44:45 +00:00
db6f1b0884 Merge "Use depth flag when fetching" 2015-01-30 19:36:06 +00:00
f2fad61bde Add missing documentation of --current-branch option on sync command
Change-Id: I72d6e3d51241148c1df97bbad26338debb1fcb4e
2015-01-29 14:36:28 +09:00
ee69084421 Merge "Handle shallow checkout of SHA1 pinned repos" 2015-01-28 20:29:37 +00:00
d37d43f036 Merge "Don't delete hooks in .git/hooks" 2015-01-28 20:29:05 +00:00
7bdac71087 pylint fixes for project.py
Fix all the formatting warnings and unused variables

Change-Id: I17d88a23572303879530077f3a80451de5417fbb
2015-01-22 04:20:21 +00:00
f97e8383a3 Use depth flag when fetching
Currently, we only use the depth flag when cloning.  The result is that when
new project history has merges, the entire history of the merged branch is
brought in and the project becomes unshallow very quickly.  --depth and
clone-depth are often used to save on space, not just network load, so this
seems less than ideal.

This change uses --depth on every fetch (when the user has depth specified),
not just the initial clone.  The result is that the given project stays
consistently shallow as opposed to growing over time, especially when merges
are involved.

Change-Id: Iac706cfdad4a555c72f9d9f1119195d38d91df12
2015-01-22 01:20:22 +00:00
3000cdad22 Handle shallow checkout of SHA1 pinned repos
When doing a shallow checkout SHA1 pinned repos with repo init --depth=1 and
repo sync -c, repo would try to fetch only some reference and fail if the exact
SHA1 repo was missing.
Instead, when depth is set, fetch only the specific commit.

Change-Id: If3f799d0e78c03faea47f796380bb5e367b11998
2015-01-21 14:14:23 -08:00
b9d9efd394 Don't delete hooks in .git/hooks
We currently delete all hooks in .git/hooks for each project before
symlink'ing in the standard project hooks.  This can be annoying for
users who have installed custom git hooks.

There's no reason to delete all existing hooks.  Just rip out the
deletion code.

Change-Id: I5062a6cd20af700f6d6a17b11ad6c94853987c57
Signed-off-by: Mitchel Humpherys <mitchelh@codeaurora.org>
2015-01-15 22:49:08 -08:00
497bde4de5 Respect --quiet when looking up bundle cookie file
Change-Id: I02a244132c49e4bb50ecda978974d6d2b220f6d1
2015-01-02 13:58:05 -08:00
4abf8e6ef8 Save cookies back to jar when fetching clone.bundle
Change-Id: I3ef71b5e7f8ee1cda66057e46ae234866c7258c4
2015-01-02 13:57:14 -08:00
137d0131bf Hold persistent proxy connection open while fetching clone.bundle
The persistent proxy may choose to present a per-process cookie file
that gets cleaned up after the process exits, to help with the fact
that libcurl cannot save cookies atomically when a cookie file is
shared across processes. We were letting this cleanup happen
immediately by closing stdin as soon as we read the configuration
option, resulting in a nonexistent cookie file by the time we use the
config option.

Work around this by converting the cookie logic to a context manager
method, which closes the process only when we're done with the cookie
file.

Change-Id: I12a88b25cc19621ef8161337144c1b264264211a
2015-01-02 13:57:13 -08:00
42e679b9f6 Merge "add a global --color option" 2015-01-02 20:56:25 +00:00
902665bce6 add a global --color option
If you want to turn off colors for commands, you have to manually adjust
the git config settings (in various locations).  If you're writing scripts
though, you often don't want to modify those locations.  Add a commandline
option to explicitly control things.

The default behavior is unchanged -- we still scan the config files.

Change-Id: I54a3fd8e1918bac180aadd7c7d3004f069b02522
2014-12-30 18:50:05 -05:00
c8d882ae2a Silence warnings about invalid clone.bundle files when quieted
The invalid clone.bundle file warning is not typically user actionable,
and can be confusing. So don't show it when -q flag is in effect.

Change-Id: If9fef4085391acf54b63c75029ec0e161c38eb86
2014-12-24 10:23:24 +09:00
3eb87cec5c Revert "Check for existence of refs upon initial fetch"
This reverts commit 565480588d.

We are reverting this change for 2 reasons:

1) It introduced a bug for users using sync -c with a reference mirror.
2) The fetch specs have recently changed to cause git to properly fail
when we request a non-existent branch of a manifest, removing the need
for this change.

Change-Id: I0f63da9bfb40cf5ffafb7979f1b8c929a738fc7b
2014-11-10 23:49:32 +00:00
5fb8ed217c If revision is sha hash and dest-branch is defined, use it for starting branch
Change-Id: I538c7d216f72b87629b61aee547d374a398c95da
2014-10-27 12:25:05 +00:00
7e12e0a2fa Support persistent-http(s) review urls
Change-Id: I8e0065685c968dfa9dc26bcdb6ee2fa14019c509
2014-10-23 15:42:09 -07:00
7893b85509 Merge changes I1f71be22,I5b119f11
* changes:
  Always fetch the specific revision given
  Support specifying non-HEADS refs as upstream
2014-10-22 00:23:18 +00:00
b4e50e67e8 Merge "upload: report names of uncommitted files" 2014-10-21 18:03:55 +00:00
0936aeab2c Exit 1 if repo download -c fails
Change-Id: I6985548bf87032b121eeccf858c4eeca1a60598c
2014-10-17 15:45:57 -04:00
14e134da02 upload: report names of uncommitted files
When there are uncommitted files in the tree, 'repo upload' stops to
ask if it is OK to continue, but does not report the actual names of
uncommitted files.

This patch adds plumbing to have the outstanding file names reported
if desired.

BUG=None
TEST=verified that 'repo upload' properly operates with the following
    conditions present in the tree:
    . file(s) modified locally
    . file(s) added to index, but not committed
    . files not known to git
    . no modified files (the upload proceeds as expected)

Change-Id: If65d5f8e8bcb3300c16d85dc5d7017758545f80d
Signed-off-by: Vadim Bendebury <vbendeb@chromium.org>
Signed-off-by: Vadim Bendebury <vbendeb@google.com>
2014-10-14 11:20:05 -07:00
04e52d6166 Always fetch the specific revision given
Don't assume the revision is in refs/heads/.

Change-Id: I1f71be222ed3ed940d2265aad43d1f2d601fc03a
2014-10-09 13:41:56 -06:00
909d58b2e2 Support specifying non-HEADS refs as upstream
While not typical, some users might have an upstream that isn't in
the usual refs/heads/* namespace. There's no reason not to use
those refs as the value for the upstream attribute, so support
doing so.

Change-Id: I5b119f1135c3268c20e7c4084682e860d3ee1fb1
2014-10-09 13:41:51 -06:00
5cf16607d3 Allow selection of a target when using smart sync.
Change-Id: I02a24471b9b62dbba3773f22a289825bc566acd9
2014-10-02 10:17:44 -07:00
c190b98ed5 Merge "Add extend-project tag to support adding groups to an existing project" 2014-09-18 23:09:08 +00:00
4863307299 Add support for rpc:// protocol schemes.
Change-Id: I0e500e45cacc20ac04b43435c4bd189299e9e97b
2014-09-10 13:45:52 -07:00
f75870beac Change implementation of cleanup in case of clone failure during "repo init"
Fix includes:
1. It deletes only .repo/repo instead of the whole .repo repository.

Bug: Issue 161
Change-Id: I1ab8caa7538fec5e6206d1b029f63bd3f60dedcd
2014-09-03 13:56:04 +05:30
bf0b0cbc2f Merge "Provide detail print-out when not all projects of a branch are current." 2014-08-26 21:11:40 +00:00
3a10968a70 Merge "Enable transferring of attribute using command 'repo manifest -o -'" 2014-08-22 16:13:16 +00:00
c46de6932a Decode git version
Used by 'repo --version'
With Python 3,
* Before: b'git version 2.1.0'
* After: git version 2.1.0

Change-Id: I4321bb0f09e92cda1123c35910338b940e82a305
2014-08-20 11:47:10 +05:30
303a82f33a Don't open non-binary files as binary
* Don't pen the git config file, and the git ".lock" file as binary.

Change-Id: I7b3939658456f2fd0a0500443cdd8d1ee1a4459d
2014-08-19 23:05:44 +05:30
7a91d51dcf Enable transferring of attribute using command 'repo manifest -o -'
'upstream' attribute is now transferred to the new manifest xml
that is created when using command 'repo manifest -o -'.

Manifest help is updated for the attributes 'sync-c','sync-s' and
'sync-j'.

Bug: Issue 164
Change-Id: If63f781e91d25c5b5b5ea0696b0c04337b0a686a
2014-07-24 16:27:08 +05:30
a8d539189e Update the commit-msg hook to the version from Gerrit 2.8.2
Change-Id: Id911bc6841f488a42d08580de800c3afafa2937e
2014-07-15 11:30:06 -07:00
588142dfcb Provide detail print-out when not all projects of a branch are current.
When current is "split" (i.e. some projects are current while others are not):
- Disable 'not in' printout (i.e. will print out all projects)
- Disable printing of multiple projects on one line
- Print current projects in green, non-current in white

Since using color to differentiate current from non-current in "split" cases:
- In non-split cases also print out project names in color (green for current
  white for non-current)

Change-Id: Ia6b826612c708447cecfe5954dc767f7b2ea2ea7
2014-07-11 10:56:03 -07:00
a6d258b84d Merge "Fix UrlInsteadOf to handle multiple strings" 2014-06-30 22:21:58 +00:00
a769498568 Add --jobs option to forall subcommand
Enable '--jobs' ('-j') option in the forall subcommand. For -jn
where n > 1, the '-p' option can no longer guarantee the
continuity of console output between the project header and the
output from the worker process.

SIG_INT is sent to all worker processes upon keyboard interrupt
(Ctrl+C).

Bug: Issue 105
Change-Id: If09afa2ed639d481ede64f28b641dc80d0b89a5c
2014-06-24 01:02:54 +00:00
884a387eca Add extend-project tag to support adding groups to an existing project
Currently, if a local manifest wants to add groups to an existing
project, it must use remove-project and then re-add the project with
the new groups.  This makes the local manifest more fragile, requiring
updates to the local manifest if the original manifest changes.

Add a new extend-project tag, which supports adding groups to an
existing project.

Change-Id: Ib4d1352efd722a65dd263d02644b9ea5ab6ed400
2014-06-20 11:35:16 -07:00
80b87fe6c1 Use fetch --unshallow when appropriate.
If a user reinits to a different manifest or the manifest updates so
that a project no longer has a fixed depth, we need to use --unshallow
when we fetch.

Change-Id: I6d3f15e5464b5eaad9205654bc24354947a78aea
2014-05-09 18:47:35 -07:00
e9f75b1782 Merge "Enable remotes to define their own revision" 2014-05-08 18:38:33 +00:00
a35e402161 Merge "Return a list rather than dict_values in XmlManifest.projects()" 2014-05-07 18:21:31 +00:00
dd7aea6c11 Merge "Define unicode as str if using Python 3" 2014-05-07 18:20:32 +00:00
5196805fa2 Merge "Use exec() rather than execfile()" 2014-05-07 18:18:56 +00:00
85b24acd6a Use JSON instead of pickle
Use JSON as it is shown to be much faster than pickle.
Also clean up the loading and saving functions.

Change-Id: I45b3dee7b4d59a1c0e0d38d4a83b543ac5839390
2014-05-07 10:46:24 +01:00
36ea2fb6ee Enable remotes to define their own revision
Some projects use multiple remotes.
In some cases these remotes have different naming conventions.
Add an option to define a revision in the remote configuration.

The `project` revision takes precedence over `remote` and `default`.
The `remote` revision takes precedence over `default`.
The `default` revision acts as a fall back as it originally did.

Change-Id: I2b376160d45d48b0bab840c02a3eef1a1e32cf6d
2014-05-07 08:29:30 +00:00
2cd1f0452e Use next(iterator) rather than iterator.next()
iterator.next() was replaced with iterator.__next__() in Python 3.
Use next(iterator) instead which will select the correct method for
returning the next item.

Change-Id: I6d0c89c8b32e817e5897fe87332933dacf22027b
2014-05-07 08:44:20 +01:00
65e3a78a9e Merge "Prevent warning twice about Python 3 usage" 2014-05-07 06:30:30 +00:00
d792f7928d Define unicode as str if using Python 3
The unicode object was renamed to str in Python 3

Change-Id: I1e4972fb07b313d3462587b3059bb3638d779625
2014-05-06 20:38:51 +01:00
6efdde9f6e Prevent warning twice about Python 3 usage
Only warn about using Python 3 when running the repo script directly.
This prevents the user being warned twice.

Change-Id: I2ee51ea2fa0127ea310598320e460ec9f38c6488
2014-05-06 12:44:22 +00:00
7446c5954a Use sorted() rather than .sort()
dict.keys() produces a dict_keys object in Python 3, which does
not support .sort(). Use sorted() which will give the same outcome.

Change-Id: If6b33db07a31995b4e44959209d08d8fb74ae339
2014-05-06 12:42:35 +00:00
d58bfe5a58 Return a list rather than dict_values in XmlManifest.projects()
dict.values() produce dict_values objects rather than list objects.
Convert this to a list to maintain functionality with certain functions.

Change-Id: Ie76269e19f8d68479a1d7ae03aa965252d759a9e
2014-05-06 09:16:52 +01:00
70f6890352 Use exec() rather than execfile()
execfile() is not in Python 3.

Change-Id: I5af222340f13c1e8edaa820e7675d3e4d62a1689
2014-05-05 23:41:07 +01:00
666d534636 Ensure HEAD is correct when skipping remote fetch
A recent optimization (2fb6466f79) skips
performing a remote fetch if we already know we have the sha1 we want.
However, that optimization skipped initialization steps that ensure HEAD
points to the correct sha1.  This change makes sure not to skip those
steps.

Here is an example of how to test this change:

"""""""""
url=<manifest url>
branch1=<branch name>
branch2=<branch name>
project=<project with revision set to different sha1 in each branch>

repo init -u $url -b $branch1 --mirror
repo sync $project
first=$(cd $project.git; git rev-parse HEAD)

repo init -b $branch2
repo sync $project
second=$(cd platform/build.git; git rev-parse HEAD)

if [[ $first == $second ]]
then
    echo 'problem!'
else
    echo 'no problem!'
fi
"""""""""
2014-05-01 13:20:32 -07:00
f2af756425 Add 'shallow' gitfile to symlinks
This fixes the bug that kept clients from doing things like `git log`
in projects using the clone-depth feature.

Change-Id: Ib4024a7b82ceaa7eb7b8935b007b3e8225e0aea8
2014-04-30 11:34:00 -07:00
544e7b0a97 Merge "Ignore clone-depth attribute when fetching to a mirror" 2014-04-24 21:21:02 +00:00
e0df232da7 Add linkfile support.
It's just like copyfile and runs at the same time as copyfile but
instead of copying it creates a symlink instead.  This is needed
because copyfile copies the target of the link as opposed to the
symlink itself.

Change-Id: I7bff2aa23f0d80d9d51061045bd9c86a9b741ac5
2014-04-22 14:35:47 -05:00
5a7c3afa73 Merge "Don't try to remove .repo if it doesn't exist" 2014-04-18 00:06:08 +00:00
9bc422f130 Ignore clone-depth attribute when fetching to a mirror
If a manifest includes projects with a clone-depth=1 attribute, and a
workspace is initialised from that manifest using the --mirror option,
any workspaces initialised and synced from the mirror will fail with:

  fatal: attempt to fetch/clone from a shallow repository

on the projects that had the clone-depth.

Ignore the clone-depth attribute when fetching from the remote to a
mirror workspace. Thus the mirror will be synched with a complete
clone of all the repositories.

Change-Id: I638b77e4894f5eda137d31fa6358eec53cf4654a
2014-04-16 11:00:40 +09:00
e81bc030bb Add total count and iteration count to forall environment
For long-running forall commands sometimes it's useful to know which
iteration is currently running. Add REPO_I and REPO_COUNT environment
variables to reflect the current iteration count as well as the total
number of iterations so that the user can build simple status
indicators.

Example:

    $ repo forall -c 'echo $REPO_I / $REPO_COUNT; git gc'
    1 / 579
    Counting objects: 41, done.
    Delta compression using up to 8 threads.
    Compressing objects: 100% (19/19), done.
    Writing objects: 100% (41/41), done.
    Total 41 (delta 21), reused 41 (delta 21)
    2 / 579
    Counting objects: 53410, done.
    Delta compression using up to 8 threads.
    Compressing objects: 100% (10423/10423), done.
    Writing objects: 100% (53410/53410), done.
    Total 53410 (delta 42513), reused 53410 (delta 42513)
    3 / 579
    ...

Change-Id: I9f28b0d8b7debe423eed3b4bc1198b23e40c0c50
Signed-off-by: Mitchel Humpherys <mitchelh@codeaurora.org>
2014-03-31 13:08:26 -07:00
eb5acc9ae9 Don't try to remove .repo if it doesn't exist
Part of the cleanup path for _Init is removing the .repo
directory. However, _Init can fail before creating the .repo directory,
so trying to remove it raises another exception:

    fatal: invalid branch name 'refs/changes/53/55053/4'
    Traceback (most recent call last):
      File "/home/mitchelh/bin/repo", line 775, in <module>
        main(sys.argv[1:])
      File "/home/mitchelh/bin/repo", line 749, in main
        os.rmdir(repodir)
    OSError: [Errno 2] No such file or directory: '.repo'

Fix this by only removing .repo if it actually exists.

Change-Id: Ia251d29e9c73e013eb296501d11c36263457e235
2014-03-12 15:11:27 -07:00
26c45a7958 Make --no-tags work with -c
Currently, the --no-tags option is ignored if the user asks to only
fetch the current branch. There is no reason for this restriction. Fix
it.

Change-Id: Ibaaeae85ebe9955ed49325940461d630d794b990
Signed-off-by: Mitchel Humpherys <mitchelh@codeaurora.org>
2014-03-12 16:34:53 +09:00
68425f4da8 Fix indentation in project.py
Change-Id: I81c630536eaa54d5a25b9cb339a96c28619815ea
2014-03-11 14:55:52 +09:00
53e902a19b More verbose errors for NoManifestExceptions.
The old "manifest required for this command -- please run
init" is replaced by a more helpful message that lists the
command repo was trying to execute (with arguments) as well
as the str() of the NoManifestException. For example:

> error: in `sync`: [Errno 2] No such file or directory:
> 	'path/to/.repo/manifests/.git/HEAD'
> error: manifest missing or unreadable -- please run init

Other failure points in basic command parsing and dispatch
are more clearly explained in the same fashion.

Change-Id: I6212e5c648bc5d57e27145d55a5391ca565e4149
2014-03-11 05:33:43 +00:00
4e4d40f7c0 Fix UrlInsteadOf to handle multiple strings
For complex .gitconfig url rewrites, multiple insteadOf lines may be
used for a url. Search all of them for the right rewrite.

Change-Id: If5e9ecd054e86226924b0baf513801cd57c389cd
2014-03-06 21:04:18 -08:00
093fdb6587 Add reviewers automatically from project's git config
The `review.URL.autocopy` setting sends email notification to the
named reviewers, but does not add them as reviewer on the uploaded
change.

Add a new setting `review.URL.autoreviewer`.  The named reviewers
will be added as reviewer on the uploaded change.

Change-Id: I3fddfb49edf346f8724fe15b84be8c39d43e7e65
Signed-off-by: bijia <bijia@xiaomi.com>
2014-03-04 00:51:30 +00:00
2fb6466f79 Don't fetch from remotes if commit id exists locally
In existing workspaces where the manifest specifies a commit id in the
manifest, we can avoid doing a fetch from the remote if we have the
commit locally. This substantially improves sync times for fully
specified manifests.

Change-Id: Ide216f28a545e00e0b493ce90ed0019513c61613
2014-03-03 10:17:03 +00:00
724aafb52d Merge "Clean up duplicate logic in subcmds/sync.py." 2014-02-28 21:16:32 +00:00
ccd218cd8f Fix to mirror manifest when --mirror is given
Commit 8d201 "repo: Support multiple branches for the same project."
(Change id is I5e2f4e1a7abb56f9d3f310fa6fd0c17019330ecd) caused missing
mirroring manifest repository when 'repo sync' after 'repo init --mirror'.

When the function _AddMetaProjectMirror() is called to add two of
meta projects - git-repo itself and manifest repository to mirror,
it didn't add them into self._paths which has list of projects to be
sync'ed by 'repo sync'.

In addition, because member var of Project 'relpath' is used as a key
of self._paths, it should be set with proper value other than None.
Since this is only for meta projects which are not described in manifest
xml, 'relpath' is name of the projects.

Change-Id: Icc3b9e6739a78114ec70bf54fe645f79df972686
Signed-off-by: Kwanhong Lee <kwanhong.lee@windriver.com>
2014-02-20 11:07:23 +09:00
dd6542268a Add the "diffmanifests" command
This command allows a deeper diff between two manifest projects.
In addition to changed projects, it displays the logs of the
commits between both revisions for each project.

Change-Id: I86d30602cfbc654f8c84db2be5d8a30cb90f1398
Signed-off-by: Julien Campergue <julien.campergue@parrot.com>
2014-02-17 11:20:11 +00:00
baca5f7e88 Merge "Add error message for download -c conflicts" 2014-02-17 07:57:00 +00:00
89ece429fb Clean up duplicate logic in subcmds/sync.py.
The fetch logic is now shared between the jobs == 1 and
jobs > 1 cases. This refactoring also fixes a bug where
opts.force_broken was not honored when jobs > 1.

Change-Id: Ic886f3c3c00f3d8fc73a65366328fed3c44dc3be
2014-02-14 16:14:32 +00:00
565480588d Check for existence of refs upon initial fetch
When we do an initial fetch and have not specified any branch etc,
the following fetch command will not error:
git fetch origin --tags +refs/heads/*:refs/remotes/origin/*

In this change we make sure something got fetched and if not we report
an error.

This fixes the bug that occurs when we init using a bad manifest url and
then are unable to init again (because a manifest project has been
inited with no manifest).

Change-Id: I6f8aaefc83a1837beb10b1ac90bea96dc8e61156
2014-02-12 09:11:00 -08:00
1829101e28 Add error message for download -c conflicts
Currently if you run repo download -c on a change and the cherry-pick
runs into a merge conflict a Traceback is produced:

rob@rob-i5-lm ~/Programming/repo_test/repo1 $ repo download -c repo1 3/1
From ssh://rob-i5-lm:29418/repo1
 * branch            refs/changes/03/3/1 -> FETCH_HEAD
error: could not apply 0c8b474... 2
hint: after resolving the conflicts, mark the corrected paths
hint: with 'git add <paths>' or 'git rm <paths>'
hint: and commit the result with 'git commit'
Traceback (most recent call last):
  File "/home/rob/Programming/git-repo/main.py", line 408, in <module>
    _Main(sys.argv[1:])
  File "/home/rob/Programming/git-repo/main.py", line 384, in _Main
    result = repo._Run(argv) or 0
  File "/home/rob/Programming/git-repo/main.py", line 143, in _Run
    result = cmd.Execute(copts, cargs)
  File "/home/rob/Programming/git-repo/subcmds/download.py", line 90, in Execute
    project._CherryPick(dl.commit)
  File "/home/rob/Programming/git-repo/project.py", line 1943, in _CherryPick
    raise GitError('%s cherry-pick %s ' % (self.name, rev))
error.GitError: repo1 cherry-pick 0c8b4740f876f8f8372bbaed430f02b6ba8b1898

This amount of error message is confusing to users and has the side effect
of the git message telling you the actual issue being ignored.

This change introduces a message stating that the cherry-pick couldn't
be completed removing the Traceback.

To reproduce the issue create a change that causes a conflict with one currently
in review and use repo download -c to cherry-pick the conflicting change.

Change-Id: I8ddf4e0c8ad9bd04b1af5360313f67cc053f7d6a
2014-02-11 18:19:04 +00:00
1966133f8e Merge "Stop appending 'p/' to review urls" 2014-02-10 22:42:31 +00:00
f1027e23b4 Merge "Implement Kerberos HTTP authentication handler" 2014-02-05 00:58:53 +00:00
2cd38a0bf8 Stop appending 'p/' to review urls
Gerrit no longer requires 'p/', and this causes unexpected behavior.
In this change we stop appending 'p/' to the urls.

Change-Id: I72c13bf838f4112086141959fb1af249f9213ce6
2014-02-04 15:32:29 -08:00
1b46cc9b6d Merge "Changes to support sso: repositories for upload" 2014-02-04 21:19:07 +00:00
1242e60bdd Implement Kerberos HTTP authentication handler
This commit implements a Kerberos HTTP authentication handler. It
uses credentials from a local cache to perform an HTTP authentication
negotiation using the GSSAPI.

The purpose of this handler is to allow the use Kerberos authentication
to access review endpoints without the need to transmit the user
password.

Change-Id: Id2c3fc91a58b15a3e83e4bd9ca87203fa3d647c8
2014-02-04 09:22:42 +01:00
2d0f508648 Fix persistent-https relative url resolving
Previously, we would remove 'persistent-' then tack it on at the end
if it had been previously found.  However, this would ignore urljoin's
decision on whether or not the second path was relative.  Instead, we
were always assuming it was relative and that we didn't want to use
a different absolute url with a different protocol.

This change handles persistent-https:// in the same way we handled the
absense of an explicit protocol.  The only difference is that this time
instead of temporarily replacing it with 'gopher://', we use 'wais://'.

Change-Id: I6e8ad1eb4b911931a991481717f1ade01315db2a
2014-01-31 16:06:31 -08:00
143d8a7249 Changes to support sso: repositories for upload
Change-Id: Iddf90d52f700a1f6462abe76d4f4a367ebb6d603
2014-01-31 07:39:44 -08:00
5db69f3f66 Update the version number on the repo launcher
The repo launcher version needs to be updated so some users can take
advantage of the more robust version number parsing.

Change-Id: Ibcd8036363311528db82db2b252357ffd21eb59b
2014-01-30 16:00:35 -08:00
ff0a3c8f80 Share git version parsing code with wrapper module
'repo' and 'git_command.py' had their own git version parsing code.
This change shares that code between the modules.  DRY is good.

Change-Id: Ic896d2dc08353644bd4ced57e15a91284d97d54a
2014-01-30 15:18:56 -08:00
094cdbe090 Add wrapper module
This takes the wrapper importing code from main.py and moves it into
its own module so that other modules may import it without causing
circular imports with main.py.

Change-Id: I9402950573933ed6f14ce0bfb600f74f32727705
2014-01-30 15:17:09 -08:00
148a84de0c Respect version hyphenation
The last change regarding version parsing lost handling of version
hyphenation, this restores that.  In otherwords,
1.1.1-otherstuff is parsed as (1,1,1) instead of (1,1,0)

Change-Id: I3753944e92095606653835ed2bd090b9301c7194
2014-01-30 13:53:55 -08:00
1c5da49e6c Handle release candidates in git version parsing
Right now repo chokes on git versions like "1.9.rc1".  This change
treats 'rc*' as a '0'.

Change-Id: I612b7b431675ba7415bf70640a673e48dbb00a90
2014-01-30 13:26:50 -08:00
b8433dfd2f repo: Fix 'remove-project' regression with multiple projects.
In CL:50715, I updated repo to handle multiple projects, but the
remove-projects code path was not updated accordingly. Update it.

Change-Id: Icd681d45ce857467b584bca0d2fdcbf24ec6e8db
2014-01-30 10:14:54 -08:00
f2fe2d9b86 Properly iterate through values
the value of Manifest.projects has changed from being the dictionary
to the values of the dictionary.  Here we handle this change
correctly on a PostRepoUpgrade.

From a `git diff v1.12.7 -- manifest_xml.py`:
+  @property
   def projects(self):
     self._Load()
-    return self._projects
+    return self._paths.values()

self._paths does contain the projects according to this line of
manifest_xml.py:
484      self._paths[project.relpath] = project

Change-Id: I141f8d5468ee10dfb08f99ba434004a307fed810
2014-01-29 13:57:22 -08:00
c9877c7cf6 Merge "Only fetch current branch on shallow clients" 2014-01-29 21:12:34 +00:00
69e04d8953 Only fetch current branch on shallow clients
Fetching a new branch on a shallow client may download the entire
project history, as the depth parameter is not passed to git
fetch. Force the fetch to only download the current branch.

Change-Id: Ie17ce8eb5e3487c24d90b2cae8227319dea482c8
2014-01-29 12:48:54 -08:00
f1f1137d61 Merge "Don't backtrace when current branch is not uploadable." 2014-01-14 00:41:35 +00:00
f77ef2edb0 Merge "hooks/pre-auto-gc: fix AC detection on OSX Maverick" 2014-01-10 02:50:53 +00:00
e695338e21 Merge "repo: Support multiple branches for the same project." 2014-01-10 01:20:13 +00:00
bd80f7eedd Merge "Canonicalize project hooks path before use" 2014-01-09 02:11:10 +00:00
bf79c6618e Fix os.mkdir race condition.
This code checks whether a dir exists before creating it. In between the
check and the mkdir call, it is possible that another process will have
created the directory. We have seen this bug occur many times in
practice during our 'repo init' tests.

Change-Id: Ia47d39955739aa38fd303f4e90be7b4c50d9d4ba
2013-12-26 14:59:00 -08:00
f045d49a71 Merge "Add --archive option to init to sync using git archive" 2013-12-18 17:44:59 +00:00
719757d6a8 hooks/pre-auto-gc: fix AC detection on OSX Maverick
The output of pmset has been changed to "Now drawing from 'AC Power'"

Change-Id: Id425d3bcd6a28656736a6d2c3096623a3ec053cc
2013-12-17 09:48:20 +07:00
011d4f426c Don't backtrace when current branch is not uploadable.
The backtrace currently occurs when one uses the "--cbr" argument with
the repo upload subcommand if the current branch is not tracking an
upstream branch. There may be other cases that would backtrace as well,
but this is the only one I found so far.

Change-Id: Ie712fbb0ce3e7fe3b72769fca89cc4c0e3d2fce0
2013-12-11 23:24:01 -08:00
53d6a7b895 Fix error in xml manifest doc.
The docs on the annotations say that zero or more may exist as a child
of a project, so that means that a "*" instead of a "?" should be used.

Change-Id: Iff855d003dfb05cd980f285a237332914e1dad70
2013-12-10 15:30:03 -08:00
335f5ef4ad Add --archive option to init to sync using git archive
This significantly reduces sync time and used brandwidth as only
a tar of each project's revision is checked out, but git is not
accessible from projects anymore.

This is relevant when git is not needed in projects but sync
speed/brandwidth may be important like on CI servers when building
several versions from scratch regularly for example.

Archive is not supported over http/https.

Change-Id: I48c3c7de2cd5a1faec33e295fcdafbc7807d0e4d
Signed-off-by: Julien Campergue <julien.campergue@parrot.com>
2013-12-10 08:27:07 +00:00
672cc499b9 Canonicalize project hooks path before use
If the top-level .repo directory is moved somewhere else (e.g. a
different drive) and replaced with a symlink, _InitHooks() will create
broken symlinks. Resolving symlinks before computing the relative path
for the symlink keeps the path within the repo tree, so the tree can
be moved anywhere.

Change-Id: Ifa5c07869e3477186ddd2c255c6c607f547bc1fe
2013-12-03 09:02:16 -08:00
61df418c59 Update the commit-msg hook to the version from Gerrit 2.6
Change-Id: Iaf21ba8d2ceea58973dbc56f0b4ece54500cd997
2013-11-29 19:17:23 +09:00
4534120628 Merge "Allow using repo with python3" 2013-11-22 10:25:35 +00:00
cbc0798f67 Fix print of git-remote-persistent-https error
If git-remote-persistent-https fails, we use an iter() and then
subsequently a .read() on stderr.  Python doesn't like this and
gives the following error message:
ValueError: Mixing iteration and read methods would lose data

This change removes the use of iter() to avoid the issue.

Change-Id: I980659b83229e2a559c20dcc7b116f8d2476abd5
2013-11-21 10:38:03 -08:00
d5a5b19efd Remove trailing whitespace
Change-Id: I56bcb559431277d40070fa33c580c6c3525ff9bc
2013-11-21 19:16:08 +05:30
5d6cb80b8f Allow using repo with python3
* Switching from python2 to python3 in the same workspace isn't
  currently supported, due to a change in the pickle version (which
  isn't supported by python2)
* Basic functionality does work with python3, however not everything
  is expected to

Change-Id: I4256b5a9861562d0260b503f972c1569190182aa
2013-11-21 18:44:52 +05:30
0eb35cbe50 Fix some python3 encoding issues
* Add .decode('utf-8') where needed
* Add 'b' to `open` where needed, and remove where unnecessary

Change-Id: I0f03ecf9ed1a78e3b2f15f9469deb9aaab698657
2013-11-21 06:03:22 +00:00
ce201a5311 Fix a small whitespace consistency issue
Change-Id: Ie98c79833ca5e7ef71666489135f7491223f779c
2013-10-16 14:42:42 -07:00
12fd10c201 Merge "Dan't accessing attr of None (manifest subcmd)" 2013-10-16 21:41:33 +00:00
a17d7af4d9 Dan't accessing attr of None (manifest subcmd)
If d.remote is None, this code failed for obvious reasons.  This is a
simple fix.

Change-Id: I413756121e444111f1e3c7dc8bc8032467946c13
2013-10-16 14:38:09 -07:00
fbd3f2a10b Only check merge destination if it isn't None
Change-Id: Ifb1dcd07142933489e93a1f4f03e38289087b609
2013-10-15 12:59:00 -07:00
37128b6f70 Fix indentation
git-repo uses 2 space indentation.  A couple of recent changes
introduced 4 space indentation in some modules.

Change-Id: Ia4250157c1824c1b5e7d555068c4608f995be9da
2013-10-15 10:48:40 +09:00
143b4cc992 Merge "Better handling of duplicate default" 2013-10-15 01:40:08 +00:00
8d20116038 repo: Support multiple branches for the same project.
It is often useful to be able to include the same project more than
once, but with different branches and placed in different paths in the
workspace. Add this feature.

This CL adds the concept of an object directory. The object directory
stores objects that can be shared amongst several working trees. For
newly synced repositories, we set up the git repo now to share its
objects with an object repo.

Each worktree for a given repo shares objects, but has an independent
set of references and branches. This ensures that repo only has to
update the objects once; however the references for each worktree are
updated separately. Storing the references separately is needed to
ensure that commits to a branch on one worktree will not change the
HEAD commits of the others.

One nice side effect of sharing objects between different worktrees is
that you can easily cherry-pick changes between the two worktrees
without needing to fetch them.

Bug: Issue 141
Change-Id: I5e2f4e1a7abb56f9d3f310fa6fd0c17019330ecd
2013-10-14 15:34:32 -07:00
53263d873d Merge "repo: use explicit Python executable to run main.py" 2013-10-10 18:42:59 +00:00
7487992bd3 Better handling of duplicate default
Currently, an error is raised if more than one default is defined.

When including another manifest, it is likely that a default has
been defined in both manifests.

Don't raise an error if all the defaults defined have the same
attributes.

Change-Id: I2603020687e2ba04c2c62c3268ee375279b34a08
Signed-off-by: Julien Campergue <julien.campergue@parrot.com>
2013-10-10 18:14:27 +02:00
b25ea555c3 Merge "Respect remote aliases" 2013-10-10 16:08:42 +00:00
3bfd72158c Don't upload when dest branch is not merge branch
Example:
- `repo init -b master` / sync a project
- In one project: `git checkout -b work origin/branch-thats-not-master`
- make some changes, `git commit`
- `repo upload .`
- Upload will now be skipped with a warning instead of being uploaded to
  master

Change-Id: I990b36217b75fe3c8b4d776e7fefa1c7d9ab7282
2013-10-10 09:06:38 -07:00
59b31cb6e0 don't pass project revision to UploadForReview
Passing a project revisionExpr to UploadForReview will cause it to
try to push to refs/for/<sha> if the revision points to a sha
instead of a branch.  Pass None for dest_branch if no destination
branch has been specified, which will cause UploadForReview to
upload to the merge branch.

There is room for further improvement, the user prompts will
still print "Upload project <project> to remote branch <sha>",
and then upload to the merge branch and not the sha, but that
is the same behavior that was in 1.12.2.

Change-Id: I06c510336ae67ff7e68b5b69e929693179d15c0b
2013-10-08 23:14:29 -07:00
1e7ab2a63f Respect remote aliases
Previously, change I7150e449341ed8655d398956a095261978d95870
had broken alias support in order to fix the manifest command to keep
it from spitting projects that point to an alias that wasn't recorded.
This commit reverts that commit and instead solves the issue more
correctly, outputting the alias in the remote node of the manifest and
respecting that alias when outputting the list of projects.

Change-Id: I941fc4adb7121d2e61cedc5838e80d3918c977c3
2013-10-08 17:26:57 -07:00
e76efdd7b3 Merge "Accept all UTF-8 committer names" 2013-09-27 17:28:45 +00:00
730ce4c3c2 Merge "Do not use print_function from __future__" 2013-09-27 17:28:08 +00:00
745a39ba3d Assume http upload if ssh_info cannot be parsed
When uploading a change for review, we sometimes request /ssh_info to
get the correct port number for uploading via ssh (regardless of
whether or not we intend to upload over ssh).

If we have trouble accessing /ssh_info (for authentication reasons,
etc), we now press on under the assumption that we will upload via http
instead of aborting.

Change-Id: Ica6bbeac1279e89424a903529649b7f4af0b6937
2013-09-27 19:15:34 +09:00
efc986c508 Merge changes I4b77af22,Ib5bc2de5
* changes:
  Sync: Improved error message when manifest server RPC call fails
  Sync: Print name of manifest server used for smart sync/smart tag
2013-09-27 02:24:39 +00:00
edd0151a26 Accept all UTF-8 committer names
Change-Id: I7d9d49a8bacf2dc332614d26cdfcc905be7a5290
2013-09-27 00:35:35 +00:00
5e0ee14575 Do not use print_function from __future__
Python 2.4 and 2.5 do not have a print_function available, so we need a
compatible print function for displaying an error message when the user
has an older version of Python.

Change-Id: I54d7297be98bb53970e873b36c6605e6dad386c3
2013-09-27 09:32:02 +09:00
70df18944a Merge "Wait for git-remote-persistent-https -print_config to exit" 2013-09-26 16:30:02 +00:00
0836a22d38 Wait for git-remote-persistent-https -print_config to exit
Change-Id: I5ab96e7c8575682217d440ddc52ecfdc8c35f179
2013-09-25 17:46:01 -07:00
b6a16e6390 Give the node _Default class a destBranchExpr
This is to avoid the following AttributeError:

line 681, in _ParseProject
AttributeError: '_Default' object has no attribute 'destBranchExpr'

Change-Id: Ia9f7e2cce1409d22d71bc8a74b33edf2b27702ca
2013-09-25 15:07:22 -07:00
351fe2c793 Sync: Improved error message when manifest server RPC call fails
When the RPC call fails, the error message returned by the server
is printed, but it is not obvious that this is caused by RPC call
failure.

Prefix the error message with a descriptive message that explains
what went wrong.

Change-Id: I4b77af22aacc2e9843c4df9d06bf54e41d9692ff
2013-09-25 19:12:13 +09:00
fb99c71939 Sync: Print name of manifest server used for smart sync/smart tag
When syncing using smart sync or smart tag mode, print the url of
the manifest server that is being used.

This is useful in organisations that have multiple manifest servers
used in different manifest branches.

Change-Id: Ib5bc2de5af6f4a942d0ef735c65cbc0721059a61
2013-09-25 19:12:06 +09:00
3a2a59eb87 repo: use explicit Python executable to run main.py
Small step to support non-POSIX platforms.

Change-Id: I3bdb9c82c2dfbacb1da328caaa1a406ab91ad675
2013-09-21 20:03:57 +03:00
bc0308478b Update gpg key for cco3@android.com
cco3@android.com has a new gpg key, so this needs to be updated in the
repo scripts so that he can sign updates.

Change-Id: I9f058263b35bd027502d6e3b814d7aeb801a1e6e
2013-07-01 11:22:01 -07:00
610d3c4e46 upload: fix display of destination branch for SingleBranch
The command `repo upload --cbr -D <some branch>` will display
the default revision, and not the actual destination branch.

Fix that and display the branch to which the change will be
uploaded to.

Change-Id: I712ed0871c819dce6774c47254dac4efec4532e0
2013-06-28 00:29:11 +00:00
033a7e91de DownloadPatchSet: fetch the change only, and nothing else.
* Previously, it would run `git fetch <remote.name> <change refspec>
  <remote.fetch>, which would fetch all the branches, even if 'sync-c'
  was set to true in the manifest.
* Fix that, since all it needs to fetch is the change that was asked
  for, and nothing else.
* For some more info, refer to the discussion on:
  I42a9d419b51f5da03f20a640ea68993cda4b6500

Change-Id: Ibc801695d56fc16e56f999e0f61393f54461785f
2013-06-13 21:14:48 +05:30
854f2b6ef4 Merge "sync: assign manifest_name earlier" 2013-06-11 13:58:47 +00:00
a892b1006b sync: assign manifest_name earlier
* manifest_name was never set if opt.smart_sync or opt.smart_tag is used.
* Set it earlier, so that the code handles it correctly when it is None.
* An UnboundLocalError is raised if running `repo sync` without any options:
  local variable 'manifest_name' referenced before assignment
* This fixes the above regression caused by commit
  53a6c5d93a

Change-Id: I57086670f3589beea8461ce0344f6ec47ab85b7b
2013-06-11 14:18:58 +05:30
db2ad9dfce Fix urllib.parse (urlparse) handling
Revert "Fix "'module' object is not callable" error", and fix it properly.

* The urlparse module is renamed to urllib.parse in Python 3.
* This commit fixes the code to use "urllib.parse.urlparse"
  instead of creating a new module urlib and setting
  urlib.parse to urlparse.urlparse.
* Fixes an AttributeError:
  'function' object has no attribute 'uses_relative'

This reverts commit cd51f17c64.

Change-Id: I48490b20ecd19cf5a6edd835506ea5a467d556ac
2013-06-11 08:21:10 +00:00
ef668c92c2 Merge "Fix a few issues with dest-branch and multiples" 2013-06-10 14:35:13 +00:00
65b162b32f Merge "Fix "'module' object is not callable" error" 2013-06-10 14:31:30 +00:00
cd51f17c64 Fix "'module' object is not callable" error
In a couple of files the urlparse method was not being set up
correctly for python < 3 and this resulted in an error being
thrown when trying to call it.

Change-Id: I4d2040ac77101e4e228ee225862f365ae3d96cec
2013-06-08 14:50:41 +09:00
53a6c5d93a Degrade: Fix smart sync/smart tag
This was broken in b2bd91c, which updated the manifest after it had
been overridden, which made it fall back to the original file (and
not the one from the manifest server).

This builds on 0766900 and overrides the manifest by the one
downloaded from the manifest server completely.

Change-Id: Ic3972390a68919b614616631d99c9e7a63c0e0db
2013-06-08 14:31:58 +09:00
c2791e85f3 Merge "Print project name for -p on mirror clients" 2013-06-05 15:54:48 +00:00
5bca9fcdd9 Print project name for -p on mirror clients
It doesn't make sense to print the relpath, since there's nothing
checked out there and the dir shouldn't even exist.

Change-Id: Id43631a8e0895929d3a5ad4ca8c2dc9e3d233e70
2013-06-03 17:52:01 -07:00
74c1f3d5e6 Read cookie file from git-remote-persistent-https if applicable
git-remote-persistent-https proxy implementations may pass cookie file
configuration to git-remote-https. When fetching bundles for
persistent-http(s) URLs, use the -print_config flag (if supported) to
extract this information from the proxy binary and pass it to curl,
overriding http.cookiefile from .gitconfig.

This adds a few ms overhead per clone.bundle fetch, which should be
acceptable since it happens only on the initial clone, which takes
much longer anyway.

Change-Id: I03be8ebaf8d3005855d33998cd8ecd412e8ec287
2013-06-04 00:12:15 +00:00
91f3ba5a3f Ensure clone.bundle files have proper header
Server auth middleware may return a 200 from a clone.bundle request
that is not a bundle file, but instead a login or access denied page.
Instead of just checking the file size, actually check the first few
bytes of the file to ensure it is a bundle file before proceeding.

Change-Id: Icea07567c568a24fd838e5cf974c58f9e4abd7c0
2013-06-04 00:12:01 +00:00
691a75936d Fix a few issues with dest-branch and multiples
This fixes dest-branch display with >1 branch being uploaded to at
once, and correctly handles setting the target branch in that case.

Change-Id: If5e9c7ece02cc0d903e2cb377485ebea73a07107
2013-06-03 10:39:43 -04:00
710d4b0391 Fix a bug in repo upload --cbr
repo upload --cbr bailed out if some branches did not have
modifications when it is used.

Change-Id: I35f264ff7d77bb4bf8f26b4c3faffa184920b6c5
2013-06-02 19:13:18 -04:00
a1f77d92c6 Merge "Handle HTTPException when attempting to get ssh_info" 2013-05-28 16:56:59 +00:00
ecf8f2b7c8 Handle HTTPException when attempting to get ssh_info
The call to `urlopen` can raise `HTTPException`, but this is not
caught which results in a python Traceback.

Add handling of the exception.  Because `HTTPException` and its
derived classes do not have any message, print the name of the
exception in the error message instead.

Change-Id: Ic90fb4cc0e92702375cd976d4a03876c8ce8bffc
2013-05-25 08:07:52 +05:30
f609f91b72 Send reviews to a different branch from fetch
This adds the ability to have reviews pushed to a different branch
from the one on which changes are based. This is useful for "gateway"
systems without smartsync.

Change-Id: I3a8a0fabcaf6055e62d3fb55f89c944e2f81569f
2013-05-24 12:17:22 -04:00
59bbb580e3 Move Python version checking to a separate module
Add a new module with methods for checking the Python version.

Instead of handling Python3 imports with try...except blocks, first
check the python version and then import the relevant modules.  This
makes the code a bit cleaner and will result in less diff when/if we
remove support for Python < 3 later.

Use the same mechanism to handle `input` vs. `raw_input` and add
suppression of pylint warnings caused by redefinition of the built-in
method `input`.

Change-Id: Ia403e525b88d77640a741ac50382146e7d635924
Also-by: Chirayu Desai <cdesai@cyanogenmod.org>
Signed-off-by: Chirayu Desai <cdesai@cyanogenmod.org>
2013-05-23 07:28:53 +00:00
da45e5d884 Remove unused show_smart option on list and info commands
Change-Id: Idf0e161a0b0cc23a5a3ee44d18cb797162cfdd7b
2013-05-16 09:32:18 +09:00
0826c0749f Merge "Disable warning about locally disabling pylint warnings" 2013-05-15 23:48:44 +00:00
de50d81c91 Disable warning about locally disabling pylint warnings
Several files have local suppression of pylint warnings.  We don't
need these to be reported; code review should catch any unnecessary
suppressions.

Change-Id: Ie71ba3e910714ef3fe44554a71bb62519d0a891d
2013-05-15 18:06:06 +09:00
2b30e3aaba Use reference also for manifest git
When running 'repo init --reference=<mirror>', the mirror will be
used for all projects except the manifest project. This is because
the _InitGitDir function uses the 'repo.reference' git config
value specified in the manifest git, which has no effect when
creating the manifest git as that value will be set after the git
has been successfully cloned.

Information about where the manifest git is located on the server
is only known when performing the 'repo init', so that information
has to be provided when cloning the git in order for it to set up
a proper mapping.

Change-Id: I47a2c8b3267a4065965058718ce1def4ecb34d5a
Signed-off-by: Chirayu Desai <cdesai@cyanogenmod.org>
2013-05-12 17:49:28 +05:30
793f90cdc0 Merge "Re-initialise repos git hooks when updating the forest" 2013-05-09 08:36:12 +00:00
d503352b14 Merge "Repo should not fetch tags for shallow projects" 2013-05-08 18:40:57 +00:00
2f992cba32 Repo should not fetch tags for shallow projects
Fetching all tags for a shallow git results in an
inconstent git and forces git to fetch more than
the depth specified.

This change teaches repo not to fetch any tags in a
repository initialised with the depth option.

Change-Id: I557ead9f88fa0d6a19b1cb55b23bba1100fcb8f2
Signed-off-by: Patrik Ryd <patrik.ryd@stericsson.com>
2013-05-08 06:54:10 +01:00
b5267f9ad2 stage: replace filter on lambda with list comprehension
To fix the pylint warning:

  W0110: map/filter on lambda could be replaced by comprehension

Change-Id: Ib914b42992bb2fbfe888a68fb7b05a7695565b63
2013-05-08 06:37:15 +01:00
45401230cf Merge "Optimise regex pattern compilation in FindProjects" 2013-05-07 20:08:13 +00:00
56f4eea26c Disable pylint warnings about similar lines in multiple files
When running pylint over the entire code base, it reports the
warning:

  R0801:  Similar lines in 2 files

for several pairs of files.

The code referred to is boilerplate code related to imports and
error handling.  It is not practical to change the code to avoid
the warnings, so simply disable them in the config.

Change-Id: Ie685fdf1cab60fb8f1503be560166a14058698d8
2013-05-04 21:40:56 +09:00
f385d0ca09 Disable warnings related to imports in pylint config
There are several modules that have imports to support various
versions of Python.  Pylint reports the following errors when
run in a version of Python that does not have the module or the
method/class in the module.

  F0401: Unable to import 'module'
  E0611: No name 'name' in module 'module'

Disable these warnings to reduce the noise on the output.

Change-Id: I97e7e2698bccb1e501a45a0869f97f90d54adfb7
2013-05-03 22:10:26 +09:00
84c4d3c345 Optimise regex pattern compilation in FindProjects
Make a list of compiled patterns once, and then iterate over that
per project, instead of compiling the patterns again on every project.

Change-Id: I91ec430d3060ec76d5e6b61facf6b13e343c90a7
2013-04-30 11:12:01 +09:00
a8864fba9f Add regex support for subcommand forall
Filter the project list based on regex or wildcard matching
of strings, then we can handle subset of all projects.

Change-Id: Ib6c23aec79e7d981f7b6a5eb0ae93c44effec467
Signed-off-by: Zhiguang Li <muzili@gmail.com>
2013-04-29 16:19:26 +05:30
275e4b727a Merge "Set correct name in PyDev and Eclipse project config" 2013-04-20 05:11:05 +00:00
c4c01f914c Merge "Some fixes for supporting python3" 2013-04-19 15:31:29 +00:00
9d5bf60d3c Set correct name in PyDev and Eclipse project config
The name of the project is shown as "repo" in the project list in
the Eclipse workspace.

This change renames it to "git-repo" to match the name of the git
repository.

The existing project in Eclipse must be removed (it is not necessary
to delete project contents on disk) and re-imported for the change to
take effect.

Change-Id: I2ac022d22f46e5361dfe49c0dbcad482aaefe628
2013-04-19 09:35:43 +09:00
217ea7d274 Some fixes for supporting python3
* Fix imports.
* Use python3 syntax.
* Wrap map() calls with list().
* Use list() only wherever needed.
  (Thanks Conley!)
* Fix dictionary iteration methods
  (s/iteritems/items/).
* Make use of sorted() in appropriate places
* Use iterators directly in the loop.
* Don't use .keys() wherever it isn't needed.
* Use sys.maxsize instead of sys.maxint

TODO:
* Make repo work fully with python3. :)

Some of this was done by the '2to3' tool [1], by
applying the needed fixes in a way that doesn't
break compatibility with python2.

Links:
[1]: http://docs.python.org/2/library/2to3.html

Change-Id: Ibdf3bf9a530d716db905733cb9bfef83a48820f7
Signed-off-by: Chirayu Desai <cdesai@cyanogenmod.org>
2013-04-18 21:35:49 +05:30
51813dfed1 repo: add rudimentary version checking
Change-Id: I957775c7ce0821971cc2320597e1a7a31950bcf3
Signed-off-by: Chirayu Desai <cdesai@cyanogenmod.org>
2013-04-17 13:43:10 +05:30
fef4ae74e2 sync: be more verbose
* Print project name if the "quiet" option is not used.

Change-Id: I99863bb50f66e4dcbaf2d170bdd05971f2a4e19a
Signed-off-by: Chirayu Desai <cdesai@cyanogenmod.org>
2013-04-15 13:38:49 +05:30
db83b1b5ab Allow mirror to be created in directories specified by 'path' attribute
In some cases, especially when local manifest files exist, users may want
to force the mirrored repositories to be created in folders according to
their 'path' attribute in the manifest, rather than according to the name
of the repositories.

To enable this functionality for specified mirror, add a new attribute
'force-path' for that project in the manifest, set its value to 'true'.

Change-Id: I61df8c987a23d84309b113e7d886ec90c838a6cc
Signed-off-by: Scott Fan <fancp2007@gmail.com>
2013-04-11 08:59:09 +08:00
ede7f12d4a Allow clone depth to be specified per project
If the clone-depth attribute is set on a project, its value will
be used to set the depth when fetching the git.  The value, if
given, must be a positive integer.

The value in the clone-depth attribute overrides any value given to
repo init via the --depth command line option.

Change-Id: I273015b3724213600b63e40cca4cafaa9f782ddf
2013-04-10 09:17:50 +09:00
04d84a23fd list: add name-only and path-only options
`repo list -n` prints only the name of the projects.
`repo list -p` prints only the path of the projects.

Change-Id: If7d78eb2651f0b1b2fe555dc286bd2bdcad0d56d
Signed-off-by: Chirayu Desai <cdesai@cyanogenmod.org>
2013-04-06 14:39:03 +05:30
0a1c6a1c16 Special handling for manifest group "default"
Change Details:
* Make "default" a special manifest group that matches any project that
  does not have the special project group "notdefault"
* Use "default" instead of "all,-notdefault" when user does not specify
  manifest group
* Expand -g option help to include example usage of manifest groups

Change Benefits:
* Allow a more intuitive and expressive manifest groups specification:
  * "default" instead of "all,-notdefault"
  * "default,foo" instead of "all,-notdefault,foo"
  * "default,-foo" instead of "all,-notdefault,-foo"
  * "foo,-default" which has no equivalent
* Default manifest groups behavior can be restored by the command
  'repo init -g default'. This is significantly more intuitive than the
  current equivalent command 'repo init -g all,-notdefault'.

Change-Id: I6d0673791d64a650110a917c248bcebb23b279d3
2013-04-03 22:27:45 +00:00
33e0456737 Fix repo manifest support of remote aliases.
Long story short, w/out this modification the manifest dump points
at the alias, rather than the actual remote for the project.  This
breaks sync'ing for scenarios where the alias doesn't have the same
repos available as the remote, plus just plain is wrong.

Change-Id: I7150e449341ed8655d398956a095261978d95870
2013-04-03 20:54:49 +00:00
07669002cb Reload the correct manifest during sync.
Fix for issue #134
https://code.google.com/p/git-repo/issues/detail?id=134

Change-Id: I94c2dea5dd63917e3f9c90cbd628921d7d61b12a
2013-03-08 16:19:03 -08:00
a0444584cb Re-initialise repos git hooks when updating the forest
Repo now re-initialises the git-hooks reference directory
when updating the forest. This allows for any new template
files to be made available throughout the project forest
when updating the forest. Previous functionality required
the user to recreate the forest.

Change-Id: I9051265028a9e77d6139791547fff095bc195077
Signed-off-by: Patrik Ryd <patrik.ryd@stericsson.com>
2013-03-08 09:09:04 +01:00
3cba0b8613 Add repoc to the .gitignore file
This is currently the only generated file not present in the .gitignore

Apparently it comes from the usage of the "imp" module in main.py

Change-Id: I685dc252d0c822818434a8e5f493f77b63a66f03
Signed-off-by: Chirayu Desai <cdesai@cyanogenmod.org>
2013-03-08 01:18:08 +00:00
a27852d0e7 Merge "Add manifest groups to the output of info" 2013-03-08 01:12:56 +00:00
61ac9ae090 Add manifest groups to the output of info
List the user's manifest groups when running `repo info`.

These groups are passed to `repo init` using the -g/--groups flag.

Change-Id: Ie8a4ed74a35b8b90df3b1ee198fe725b1cd68ae7
2013-03-07 09:47:29 -08:00
3ee6ffd078 Merge "Fix: Missing spaces in printed messages" 2013-03-06 00:46:45 +00:00
28db6ffef4 Merge "Fix: local manifest deprecation warning appears more than once" 2013-03-06 00:46:20 +00:00
2f9e7e40c4 Fix: Missing spaces in printed messages
Several messages are printed with the `print` method and the message
is split across two lines, i.e.:

 print('This is a message split'
       'across two source code lines')

Which causes the message to be printed as:

 This is a message splitacross two source code lines

Add a space at the end of the first line before the line break:

 print('This is a message split '
       'across two source code lines'

Also correct a minor spelling mistake.

Change-Id: Ib98d93fcfb98d78f48025fcc428b6661380cff79
2013-03-05 17:30:59 +09:00
45d21685b9 upload: support --re and --cc options over HTTP
HTTP can't use the older style of passing options as part of
the git receive-pack command line. Use the new style as defined
by https://gerrit-review.googlesource.com/42652 when connecting
over HTTP.

If the Gerrit server is too old to understand the % option
syntax used here one of two outcomes is possible:

- If no topic name was sent the server will fail with an error
  message. This happens because the user tried to do an upload to
  "refs/for/master%r=alice", and the branch does not exist.
  The user can delete the options and retry the upload.

- If a topic was set the options will be read as part of the
  topic string and shown on the change page in the topic field.

Either outcome is slightly better than the current behavior of
just dropping the data on the floor and forgetting whatever the
user tried to supply.

Change-Id: Ib2df62679e5bf3ee93d6b18c12ab6474f96d9106
2013-02-28 12:10:31 -08:00
597868b4c4 Add --no-tags option to prevent fetching of tags
Add an option to pass `--no-tags' to `git fetch'.

Change-Id: I4158cc369773e08e55a167091c38ca304a197587
Signed-off-by: Mitchel Humpherys <mitchelh@codeaurora.org>
2013-02-27 11:00:49 +09:00
75b4c2deac Fix crash in repo info when % is used in commit messages
Fix for issue #131
http://code.google.com/p/git-repo/issues/detail?id=131

Change-Id: I078533ab5f3a83154c4ad6aa97a5525fc5139d20
2013-02-26 16:05:26 +09:00
b75415075c Add nofmt_printer to color.py
The current printer always expands on the arguments which is a problem
for strings containing %.

Instead of forcing manual string expansion before printing allow for a
no format printer option which simply accepts and prints the string.

Part of fix for issue #131:
http://code.google.com/p/git-repo/issues/detail?id=131

Change-Id: I08ef94b9c4ddab58ac12d2bd32ebd2c413e4f83b
2013-02-26 16:04:55 +09:00
4eb285cf90 Fix: local manifest deprecation warning appears more than once
When running repo sync, the local_manifest.xml deprecation warning
is shown twice.

Add a flag to ensure that it is only displayed once.

Change-Id: Icfa2b0b6249c037c29771f9860252e6eda3ae651
2013-02-17 21:23:33 +09:00
5f434ed723 Exit with fatal error if local manifest file cannot be parsed
If the .repo/local_manifests folder includes a local manifest file
that cannot be parsed, the current behaviour is to catch the parse
exception, print a warning, and continue to process remaining files.

This can cause any errors to go unnoticed.

Remove the exception handling, so that the exception is instead
caught in main._Main, and repo exits with a fatal error.

Change-Id: I75a70b7b850d2eb3e4ac99d435a4568ff598b7f4
2013-02-17 21:20:20 +09:00
606eab8043 Show full path of local_manifests folder in deprecation warning
When a local_manifest.xml file is present, a deprecation warning
is printed telling the user to put local manifest files in the
`local_manifests` directory.

Include the full path to the `local_manifests` directory in the
warning, to reduce confusion about where the directory should be
located.  Also enclose the directory name in backticks.

Change-Id: I85710cfbd6e77fb2fa6b7b0ce66d77693ccd649f
2013-02-17 21:19:58 +09:00
cd07cfae1c Merge "Protect line endings in shell scripts" 2013-02-14 07:51:36 +00:00
55693aabe5 Update the commit-msg hook to the version from Gerrit 2.5.2
Change-Id: I00760fe55a0e1b61375a378c05f263e7bc857ca0
2013-02-13 09:56:09 +09:00
23bd3a1dd3 Add missing sys module when referencing stderr
`repo cherry-pick` was broken because we were referencing stderr
instead of sys.stderr.  This should fix it.

Change-Id: I67f25c3a0790d029edc65732c319df7c684546c8
2013-02-12 13:46:14 -08:00
bbf71fe363 Protect line endings in shell scripts
Add a .gitattributes file to prevent /bin/sh scripts from
getting clobbered by git config core.autocrlf=true setting.

Change-Id: I3dfc992a9c275fceae64c9719168d81e60d911bd
2013-02-11 22:13:39 +01:00
91f011ab0d Merge "Don't exit with error on HTTP 403 when downloading clone bundle" 2013-02-04 10:58:48 +00:00
87b9d9b4f2 Don't exit with error on HTTP 403 when downloading clone bundle
If the server returns HTTP 403 (forbidden) when attempting to
download clone bundle files, ignore it and continue, rather than
exiting with a fatal error.

Change-Id: Icf78cba0332b51b0e7b622f7c7924369b551b6f6
2013-01-31 21:12:08 +09:00
57bd7b717b Fix: GitError when using repo info -d
If a workspace is initialised with:

 repo init -u git://path/to/manifest -b manifest-branch

and the default.xml specifies the default revision as `other-branch`,
running `repo info -d` results in a GitError:

  fatal: bad revision 'refs/remotes/m/other-branch..'

The repo info command uses the default revision to build the symlink
to the remote revision which is passed to the `git rev-list` command.

This is incorrect; the manifest's branch name should be used.

Change-Id: Ibae5b91869848276785facfaef433e38d49fd726
2013-01-29 18:18:20 +09:00
4e46520362 Add missing manifest format documentation
Documentation of the "sync-j", "sync-c", "sync-s" and "upstream"
attributes is missing/incomplete.  Add it.

Change-Id: I74977f819f603c520ef3818f85c3b51399cd2b94
2013-01-29 10:09:21 +09:00
63d356ffce 'repo status --orphans' shows non-repo files
'repo status --orphans' searches for non-repo objects
(not within a project), which is particularly helpful
before removing a working tree.

Change-Id: I2239c12e6bc0447b0ad71129551cb50fa671961c
2013-01-29 10:01:53 +09:00
35765966bf Fix: missing space in information message after repo init
In the information message displayed after running repo init, there
is a missing space:

  If this is not the directory in which you want to initializerepo

Add a space.

Change-Id: I20467673ba7481cfe782ba58ff6ed2f7ce9824a5
2013-01-29 09:52:22 +09:00
254709804d Better error message when using --mirror in existing workspace
If repo init is run with the --mirror option, repo checks if there
is already a workspace initialized in the current location, and if
so, exits with an error message:

  --mirror not supported on existing client

This error can cause confusion; the users do not understand what
is wrong and what they need to do to fix it.

Change the error message to make it a bit clearer.

Change-Id: Ifd06ef64fd264bd1117e4184c49afe0345b75d8c
2013-01-29 09:47:07 +09:00
e0b6de32f7 Fix: missing spaces in info command output
Text should be joined with " " rather than "" in the output.

Change-Id: I6c5dddc15743e98c3b43702cb5d3ec32f81c3221
2013-01-29 00:02:46 +00:00
4baf87f92c Merge "should use os.path.lexist instead of os.path.exist" 2013-01-28 16:25:55 +00:00
84f7e137c2 Merge "Check for a cookie file when fetching clone.bundle." 2013-01-17 20:02:03 +00:00
26e2475a0f If parsing the manifests fails, reset the XmlManifest object
so that if it's called again, we see the correct errors.

Change-Id: I909488feeac04aecfc92a9b5d6fb17827ef2f213
2013-01-17 09:10:00 -08:00
c59bafffb9 Merge "Pass full path of the XML in local_manifests to the parser" 2013-01-17 17:06:49 +00:00
0290cad5db Merge "Make -notdefault a default manifest group" 2013-01-14 22:34:13 +00:00
ed68d0e852 Check for a cookie file when fetching clone.bundle.
If the user's git config specifies a cookie file for HTTP, use it when
fetching clone.bundle, as it may contain the required login credentials
to get access (e.g. when used with Compute Engine service accounts).

Change-Id: I12ee9e38694822ef1de4ed62138c3876c43f431b
2013-01-11 16:22:54 +00:00
1a5c774cbf Pass full path of the XML in local_manifests to the parser
This fixes the following python error message if the
current working directory is not .repo/local_manifests:
IOError: [Errno 2] No such file or directory: 'local_manifest.xml'

Signed-off-by: Tobias Droste <tdroste87@gmail.com>
CC: David Pursehouse <david.pursehouse@sonymobile.com>
Change-Id: I4668dc04219b6233c7ff6ca3b4138bec9ee3529f
2013-01-07 07:46:18 +01:00
a9f11b3cb2 Support resolving relative fetch URLs on persistent-https://
Some versions of Python will only attempt to resolve a relative
URL if they understand the URL scheme. Convert persistent-http://
and persistent-https:// schemes to the more typical http:// and
https:// versions for the resolve call.

Change-Id: I99072d5a69be8cfaa429a3ab177ba644d928ffba
2013-01-02 15:41:57 -08:00
0c635bb427 Make -notdefault a default manifest group
When trying to render manifest with SHAs, projects in group notdefault
caused the following crash:

    Traceback (most recent call last):
      File ".repo/repo/main.py", line 385, in <module>
        _Main(sys.argv[1:])
      File ".repo/repo/main.py", line 365, in _Main
        result = repo._Run(argv) or 0
      File ".repo/repo/main.py", line 137, in _Run
        result = cmd.Execute(copts, cargs)
      File ".repo/repo/subcmds/manifest.py", line 129, in Execute
        self._Output(opt, manifest)
      File ".repo/repo/subcmds/manifest.py", line 79, in _Output
        peg_rev = opt.peg_rev)
      File ".repo/repo/manifest_xml.py", line 199, in Save
        p.work_git.rev_parse(HEAD + '^0'))
      File ".repo/repo/project.py", line 2035, in runner
        capture_stderr = True)
      File ".repo/repo/git_command.py", line 215, in __init__
        raise GitError('%s: %s' % (command[1], e))
    error.GitError: rev-parse: [Errno 2] No such file or directory: 'prebuilts/eclipse-build-deps'

This patch resolves the issue by making sure that -notdefault is always
used as a default manifest group so that notdefault projects are not
rendered out by the manifest subcmd.

Change-Id: I4a8bd18ea7600309f39ceff1b1ab6e1ff3adf21d
Signed-off-by: Matt Gumbel <matthew.k.gumbel@intel.com>
2012-12-21 10:14:53 -08:00
7bdbde7af8 Allow sync to run even when the manifest is broken.
If the current manifest is broken then "repo sync" fails because it
can't retrieve the default value for --jobs. Use 1 in this case, in
order that you can "repo sync" to get a fixed manifest (assuming someone
fixed it upstream).

Change-Id: I4262abb59311f1e851ca2a663438a7e9f796b9f6
2012-12-05 11:01:36 +00:00
223bf963f0 should use os.path.lexist instead of os.path.exist
The logic of the program requires a check on the existence of the
link itself

See repo  issue #125  :
        https://code.google.com/p/git-repo/issues/detail?id=125

Change-Id: Ia7300d22d6d656259f47c539febf1597f0c35538
2012-11-23 10:53:12 +01:00
b2bd91c99b Represent git-submodule as nested projects, take 2
(Previous submission of this change broke Android buildbot due to
 incorrect regular expression for parsing git-config output.  During
 investigation, we also found that Android, which pulls Chromium, has a
 workaround for Chromium's submodules; its manifest includes Chromium's
 submodules.  This new change, in addition to fixing the regex, also
 take this type of workarounds into consideration; it adds a new
 attribute that makes repo not fetch submodules unless submodules have a
 project element defined in the manifest, or this attribute is
 overridden by a parent project element or by the default element.)

We need a representation of git-submodule in repo; otherwise repo will
not sync submodules, and leave workspace in a broken state.  Of course
this will not be a problem if all projects are owned by the owner of the
manifest file, who may simply choose not to use git-submodule in all
projects.  However, this is not possible in practice because manifest
file owner is unlikely to own all upstream projects.

As git submodules are simply git repositories, it is natural to treat
them as plain repo projects that live inside a repo project.  That is,
we could use recursively declared projects to denote the is-submodule
relation of git repositories.

The behavior of repo remains the same to projects that do not have a
sub-project within.  As for parent projects, repo fetches them and their
sub-projects as normal projects, and then checks out subprojects at the
commit specified in parent's commit object.  The sub-project is fetched
at a path relative to parent project's working directory; so the path
specified in manifest file should match that of .gitmodules file.

If a submodule is not registered in repo manifest, repo will derive its
properties from itself and its parent project, which might not always be
correct.  In such cases, the subproject is called a derived subproject.

To a user, a sub-project is merely a git-submodule; so all tips of
working with a git-submodule apply here, too.  For example, you should
not run `repo sync` in a parent repository if its submodule is dirty.

Change-Id: I4b8344c1b9ccad2f58ad304573133e5d52e1faef
2012-11-19 10:45:21 -08:00
3f5ea0b182 Allow init command to set options from environment variables
The manifest URL and mirror location can be specified in environment
variables which will be used if the options are not passed on the
command line

Change-Id: Ida87968b4a91189822c3738f835e2631e10b847e
2012-11-17 12:40:42 +09:00
b148ac9d9a Allow command options to be set from environment variables
Extend the Command base class to allow options to be set from values
in environment variables, if the user has not given the option on the
command line and the environment variable is set.

Derived classes of Command can override the implementation of the method
_GetEnvironmentOptions to configure which of its options may be set from
environment variables.

Change-Id: I7c780bcf9644d6567893d9930984c054bce7351e
2012-11-17 12:40:42 +09:00
a67df63ef1 Merge "Raise a NoManifestException when the manifest DNE" 2012-11-16 10:39:24 -08:00
f91074881f Better error message if 'remove-project' refers to non-existent project
If a local manifest includes a 'remove-project' element that refers to
a project that does not exist in the manifest, the error message is a
bit cryptic.

Change the error message to make it clearer what is wrong.

Change-Id: I0b1043aaec87893c3128211d3a9ab2db6d600755
2012-11-16 19:12:55 +09:00
75ee0570da Raise a NoManifestException when the manifest DNE
When a command (eg, `repo forall`) expects the manifest project to
exist, but there is no manifest, an IOException gets raised.  This
change defines a new Exception type to be raised in these cases and
raises it when project.py fails to read the manifest.

Change-Id: Iac576c293a37f7d8f60cd4f6aa95b2c97f9e7957
2012-11-15 18:50:11 -08:00
88b86728a4 Add option to abort on error in forall
Add a new option (-e, --abort-on-errors) which will cause forall to
abort without iterating through remaining projects if a command
exits unsuccessfully.

Bug: Issue 17
Change-Id: Ibea405e0d98b575ad3bda719d511f6982511c19c
Signed-off-by: Victor Boivie <victor.boivie@sonyericsson.com>
2012-11-16 04:22:10 +09:00
e66291f6d0 Merge "Simplify error handling in subcommand execution" 2012-11-14 16:22:41 -08:00
7ba25bedf9 Simplify error handling in subcommand execution
Instead of using a nested try (which repo is plagued with), use a single
try when executing the appropriate subcommand.

Change-Id: I32dbfc010c740c0cc42ef8fb6a83dfe87f87e54a
2012-11-14 14:18:06 -08:00
3794a78b80 Sync help text in repo from init.py
Change Ia6032865f9296b29524c2c25b72bd8e175b30489 improved the
help text for the init command, but the same improvement was not made
in repo.

Change-Id: Idc34e479b5237137b90e8b040824776e4f7883b0
2012-11-15 06:21:24 +09:00
33949c34d2 Add repo info command
The info command will print information regarding the current manifest
and local git branch. It will also show the difference of commits
between the local branch and the remote branch.

It also incorporates an overview command into info which shows commits
over all branches.

Change-Id: Iafedd978f44c84d240c010897eff58bbfbd7de71
2012-11-15 03:29:01 +09:00
8f62fb7bd3 Tidy up code formatting a bit more
Enable the following Pylint warnings:

  C0322: Operator not preceded by a space
  C0323: Operator not followed by a space
  C0324: Comma not followed by a space

And make the necessary fixes.

Change-Id: I74d74283ad5138cbaf28d492b18614eb355ff9fe
2012-11-14 12:09:38 +09:00
98ffba1401 Fix: "Statement seems to have no effect"
Pylint raises an error on the call:

  print

Change it to:

 print()

Change-Id: I507e1b3dd928fa6c32ea7e86260fb3d7b1428e6f
2012-11-14 11:38:57 +09:00
c1b86a2323 Fix inconsistent indentation
The repo coding style is to indent at 2 characters, but there are
many places where this is not followed.

Enable pylint warning "W0311: Bad indentation" and make sure all
indentation is at multiples of 2 characters.

Change-Id: I68f0f64470789ce2429ab11104d15d380a63e6a8
2012-11-14 11:38:57 +09:00
cecd1d864f Change print statements to work in python3
This is part of a series of changes to introduce Python3 support.

Change-Id: I373be5de7141aa127d7debdbce1df39148dbec32
2012-11-13 17:33:56 -08:00
fc241240d8 Convert prompt answers to lower case before checking
When prompting for yes/no answers, convert the answer to lower
case before comparing.  This makes it easier to catch answers
like "Yes", "yes", and "YES" with a comparison only for "yes".

Change-Id: I06da8281cec81a7438ebb46ddaf3344d12abe1eb
2012-11-14 09:19:39 +09:00
9f3406ea46 Minor documentation formatting and grammatical fixes
Change-Id: Iaac6377c787b3bb42242780e9d1116e718e0188d
2012-11-14 08:54:43 +09:00
b1525bffae Fix documentation reference to local_manifest.xml
Documentation of the remove-project element still refers explicitly
to local_manifest.xml.

Change it to the more generic "a local manifest".

Change-Id: I6278beab99a582fae26a4e053adc110362c714c2
2012-11-14 08:54:04 +09:00
685f080d62 More code style cleanup
Clean up a few more unnecessary usages of lambda in `repo` that were missed
in the previous sweep that only considered files ending in .py.

Remove a duplicate import.

Change-Id: I03cf467a5630cbe4eee6649520c52e94a7db76be
2012-11-14 08:34:39 +09:00
8898e2f26d Remove magic hack
It should be assumed that on modern development environments, python
is accessible to /usr/bin/env

Change the shebang as necessary and remove the magic hack.

This also means losing the -E option on the call to python, so that
PYTHONPATH and PYTHONHOME will be respected and local configuration
problems in those vars would be noticed

Change-Id: I6f0708ca7693f05a4c3621c338f03619563ba630
2012-11-14 08:17:11 +09:00
52f1e5d911 Make load order of local manifests deterministic
Local manifest files stored in the local_manifests folder are loaded
in alphabetical order, so it's easier to know in which order project
removals/additions/modifications will be applied.

If local_manifests.xml exists, it will be loaded before the files in
local_manifests.

Change-Id: Ia5c0349608f1823b4662cd6b340b99915bd973d5
2012-11-14 05:05:32 +09:00
8e3d355d44 Merge "Print an error message when aborted by user" 2012-11-12 17:35:47 -08:00
4a4776e9ab Merge "Handle manifest parse errors in main" 2012-11-12 17:35:40 -08:00
2fa715f8b5 Merge "Better handling of duplicate remotes" 2012-11-12 17:35:30 -08:00
6287543e35 Merge "Change usages of xrange() to range()" 2012-11-12 17:30:55 -08:00
b0936b0e20 Print an error message when aborted by user
Change-Id: If7378c5deaace0ac6ab2be961e38644d9373557d
2012-11-13 09:56:16 +09:00
0b8df7be79 Handle manifest parse errors in main
Add handling of manifest parse errors in the main method, and
print an error.  This will prevent python tracebacks being
dumped in many cases.

Change-Id: I75e73539afa34049f73c993dbfda203f1ad33b45
2012-11-13 09:54:47 +09:00
717ece9d81 Better handling of duplicate remotes
In the current implementation, an error is raised if a remote with the
same name is defined more than once.  The check is only that the remote
has the same name as an existing remote.

With the support for multiple local manifests, it is more likely than
before that the same remote is defined in more than one manifest.

Change the check so that it only raises an error if a remote is defined
more than once with the same name, but different attributes.

Change-Id: Ic3608646cf9f40aa2bea7015d3ecd099c5f5f835
2012-11-13 09:35:37 +09:00
5566ae5dde Print deprecation warning when local_manifest.xml is used
The preferred way to specify local manifests is to drop the file(s)
in the local_manifests folder.  Print a deprecation warning when
the legacy local_manifest.xml file is used.

Change-Id: Ice85bd06fb612d6fcceeaa0755efd130556c4464
2012-11-13 08:19:51 +09:00
2d5a0df798 Add support for multiple local manifests
Add support for multiple local manifests stored in the local_manifests
folder under the .repo home directory.

Local manifests will be processed in addition to local_manifest.xml.

Change-Id: Ia0569cea7e9ae0fe3208a8ffef5d9679e14db03b
2012-11-13 08:19:51 +09:00
f7fc8a95be Handle XML errors when parsing the manifest
Catch ExpatError and exit gracefully with an error message, rather
than exiting with a python traceback.

Change-Id: Ifd0a7762aab4e8de63dab8a66117170a05586866
2012-11-13 05:53:41 +09:00
1ad7b555df Merge "Always show --manifest-server-* options" 2012-11-07 12:39:25 -08:00
7e6dd2dff0 Fix pylint warning W0108: Lambda may not be necessary
Remove unnecessary usage of lambda.

Change-Id: I06d41933057d60d15d307ee800cca052a44754c6
2012-11-07 08:39:57 +09:00
8d070cfb25 Always show --manifest-server-* options
The --manifest-server-* flags broke the smartsync subcmd since
the corresponding variables weren't getting set.  This change
ensures that they will always be set, regardless of whether we are
using sync -s or smartsync.

Change-Id: I1b642038787f2114fa812ecbc15c64e431bbb829
2012-11-06 13:14:31 -08:00
a6053d54f1 Change usages of xrange() to range()
In Python3, range() creates a generator rather than a list.

None of the parameters in the ranges changed looked large enough
to create an impact in memory in Python2.  Note: the only use of
range() was for iteration and did not need to be changed.

This is part of a series of changes to introduce Python3 support.

Change-Id: I50b665f9296ea160a5076c71f36a65f76e47029f
2012-11-01 13:36:50 -07:00
e072a92a9b Merge "Use python3 urllib when urllib2 not available" 2012-11-01 10:13:34 -07:00
7601ee2608 Merge "Use 'stat' package instead of literals for mkdir()" 2012-11-01 10:01:18 -07:00
1f7627fd3c Use python3 urllib when urllib2 not available
This is part of a series of changes to introduce Python3 support.

Change-Id: I605b145791053c1f2d7bf3c907c5a68649b21d12
2012-10-31 14:26:48 -07:00
b42b4746af project: Require git >= 1.7.2 for setting config on command line
This option causes the git call to fail, which probably indicates a
programming error; callers should check the git version and change the
call appropriately if -c is not available. Failing loudly is preferable
to failing silently in the general case.

For an example of correctly checking at the call site, see I8fd313dd.
If callers prefer to fail silently, they may set GIT_CONFIG_PARAMETERS
in the environment rather than using the config kwarg to pass
configuration.

Change-Id: I0de18153d44d3225cd3031e6ead54461430ed334
2012-10-31 12:27:27 -07:00
e21526754b sync: Only parallelize gc for git >= 1.7.2
This minimum version is required for the -c argument to set config on
the command line. Without this option, git by default uses as many
threads per invocation as there are CPUs, so we cannot safely
parallelize without hosing a system.

Change-Id: I8fd313dd84917658162b5134b2d9aa34a96f2772
2012-10-31 12:27:17 -07:00
60798a32f6 Use 'stat' package instead of literals for mkdir()
This is part of a series of changes to introduce Python3 support.

Change-Id: Ic988ad181d32357d82dfa554e70d8525118334c0
2012-10-31 09:11:16 -07:00
1d947b3034 Even more coding style cleanup
Fixing some more pylint warnings:

W1401: Anomalous backslash in string
W0623: Redefining name 'name' from outer scope
W0702: No exception type(s) specified
E0102: name: function already defined line n

Change-Id: I5afcdb4771ce210390a79981937806e30900a93c
2012-10-30 10:28:20 +09:00
2d113f3546 Merge "Update minimum git version to 1.7.2" 2012-10-26 16:10:21 -07:00
de7eae4826 Merge "Revert "Represent git-submodule as nested projects"" 2012-10-26 12:30:38 -07:00
2fe99e8820 Merge "repo selfupdate: Fix _PostRepoUpgrade takes 2 arguments" 2012-10-26 12:27:36 -07:00
cd81dd6403 Revert "Represent git-submodule as nested projects"
This reverts commit 69998b0c6f.
Broke Android's non-gitmodule use case.

Conflicts:
	project.py
	subcmds/sync.py

Change-Id: I68ceeb63d8ee3b939f85a64736bdc81dfa352aed
2012-10-26 12:24:57 -07:00
80d2ceb222 repo selfupdate: Fix _PostRepoUpgrade takes 2 arguments
Change-Id: I1cf9e0674ea366ddce96c949e0bc085e3452b25a
2012-10-26 12:24:57 -07:00
c5aa4d3528 Update minimum git version to 1.7.2
We now use the -c flag which was introduced in git 1.7.2.

Change-Id: I9195c0f6ac9fa63e783a03628049fe2c67d258ff
2012-10-26 11:34:11 -07:00
bed45f9400 Merge "Show user about not initializing repo in current directory" 2012-10-26 09:52:16 -07:00
55e4d464a7 Add a PGP key for cco3@android.com
This change adds a PGP key to allow cco3@android.com to sign releases.

Change-Id: I18a70c8b7d8f272dd1aad9d6b2e4a237ef35af33
2012-10-26 07:03:59 -07:00
75cc353380 Show user about not initializing repo in current directory
If the parent of current directory has an initialized repo,
for example, if the current directory is
'/home/users/harry/platform/ics', and there is an initialized repo
in harry's home directory '/home/users/harry/.repo', when user
run 'repo init' command, repo is always initialized to parent
directory in '/home/users/harry/.repo', but most of time user
intends to initialize repo in the current directory, this patch
tells user how to do it.

Change-Id: Id7a76fb18ec0af243432c29605140d60f3de85ca
2012-10-26 15:40:17 +08:00
c9129d90de Update PGP keys during _PostRepoUpgrade in sync
Previously, if a key was added, a client wouldn't add the key during
the sync step.  This would cause issues if a new key were added and a
subsequent release were signed by that key.

Change-Id: I4fac317573cd9d0e8da62aa42e00faf08bfeb26c
2012-10-25 17:48:35 -07:00
57365c98cc Merge "sync: Run gc --auto in parallel" 2012-10-25 17:38:05 -07:00
dc96476af3 Merge "project: Support config args in git command callables" 2012-10-25 17:36:04 -07:00
2577cec095 Merge "sync: Keep a moving average of last fetch times" 2012-10-25 17:35:15 -07:00
e48d34659e Merge "sync: Order projects according to last fetch time" 2012-10-25 17:33:36 -07:00
ab8f911a67 Fix pylint warnings introduced by the submodule patch
"69998b0 Represent git-submodule as nested projects" has introduced a
few pylint warnings.

W0612:1439,8:Project._GetSubmodules.get_submodules: Unused variable 'sub_gitdir'
W0613:1424,36:Project._GetSubmodules.get_submodules: Unused argument 'path'
W0612:1450,25:Project._GetSubmodules.parse_gitmodules: Unused variable 'e'
W0622:516,8:Sync.Execute: Redefining built-in 'all'

Change-Id: I84378e2832ed1b5ab023e394d53b22dcea799ba4
2012-10-25 13:55:49 -07:00
608aff7f62 Merge "Use modern Python exception syntax" 2012-10-25 10:03:37 -07:00
13657c407d Merge "Add regex matching to repo list command" 2012-10-25 10:00:42 -07:00
e4ed8f65f3 Merge "Add pylint configuration and instructions" 2012-10-25 09:51:07 -07:00
fdb44479f8 Merge "Change PyDev project version to "python 2.6"" 2012-10-25 09:46:38 -07:00
188572170e sync: Run gc --auto in parallel
We can't just let this run wild with a high (or even low) -j, since
that would hose a system. Instead, limit the total number of threads
across all git gc subprocesses to the number of CPUs reported by the
multiprocessing module (available in Python 2.6 and above).

Change-Id: Icca0161a1e6116ffa5f7cfc6f5faecda510a7fb9
2012-10-25 08:12:48 -07:00
d75c669fac Add regex matching to repo list command
The repo list -r command will execute a regex search for every
argument provided on both the project name and the project
worktree path.

Useful for finding rarely used gits.

Change-Id: Iaff90dd36c240b3d5d74817d11469be22d77ae03
2012-10-25 15:49:13 +09:00
091f893625 project: Support config args in git command callables
Change-Id: I9d4d0d2b1aeebe41a6b24a339a154d258af665eb
2012-10-24 14:52:08 -07:00
d947858325 sync: Keep a moving average of last fetch times
Try to more accurately estimate which projects take the longest to
sync by keeping an exponentially weighted moving average (a=0.5) of
fetch times, rather than just recording the last observation. This
should discount individual outliers (e.g. an unusually large project
update) and hopefully allow truly slow repos to bubble to the top.

Change-Id: I72b2508cb1266e8a19cf15b616d8a7fc08098cb3
2012-10-24 14:52:07 -07:00
67700e9b90 sync: Order projects according to last fetch time
Some projects may consistently take longer to fetch than others, for
example a more active project may have many more Gerrit changes than a
less active project, which take longer to transfer. Use a simple
heuristic based on the last fetch time to fetch slower projects first,
so we do not tend to spend the end of the sync fetching a small number
of outliers.

This algorithm is probably not optimal, and due to inter-run latency
variance and Python thread scheduling, we may not even have good
estimates of a project sync time.

Change-Id: I9a463f214b3ed742e4d807c42925b62cb8b1745b
2012-10-24 14:51:58 -07:00
a5be53f9c8 Use modern Python exception syntax
"except Exception as e" instead of "except Exception, e"

This is part of a transition to supporting Python 3.  Python >= 2.6
support "as" syntax.

Note: this removes Python 2.5 support.

Change-Id: I309599f3981bba2b46111c43102bee38ff132803
2012-10-23 21:35:59 -07:00
9ed12c5d9c Change PyDev project version to "python 2.6"
Repo is dropping support for Python <2.5 soon, so this updates the
PyDev configuration appropriately.

Change-Id: If327951e3a9fd9ff7513b931bfcfe6172dc8e4c5
2012-10-23 21:35:46 -07:00
4f7bdea9d2 Add pylint configuration and instructions
pylint configuration file (.pylintrc) is added, and submission
instructions are updated to include pylint usage steps.

Deprecated pylint suppression (`disable-msg`) is updated in a few
modules to make it work properly with the latest version (0.26).

Change-Id: I4ec2ef318e23557a374ecdbf40fe12645766830c
2012-10-24 10:18:13 +09:00
69998b0c6f Represent git-submodule as nested projects
We need a representation of git-submodule in repo; otherwise repo will
not sync submodules, and leave workspace in a broken state.  Of course
this will not be a problem if all projects are owned by the owner of the
manifest file, who may simply choose not to use git-submodule in all
projects.  However, this is not possible in practice because manifest
file owner is unlikely to own all upstream projects.

As git submodules are simply git repositories, it is natural to treat
them as plain repo projects that live inside a repo project.  That is,
we could use recursively declared projects to denote the is-submodule
relation of git repositories.

The behavior of repo remains the same to projects that do not have a
sub-project within.  As for parent projects, repo fetches them and their
sub-projects as normal projects, and then checks out subprojects at the
commit specified in parent's commit object.  The sub-project is fetched
at a path relative to parent project's working directory; so the path
specified in manifest file should match that of .gitmodules file.

If a submodule is not registered in repo manifest, repo will derive its
properties from itself and its parent project, which might not always be
correct.  In such cases, the subproject is called a derived subproject.

To a user, a sub-project is merely a git-submodule; so all tips of
working with a git-submodule apply here, too.  For example, you should
not run `repo sync` in a parent repository if its submodule is dirty.

Change-Id: I541e9e2ac1a70304272dbe09724572aa1004eb5c
2012-10-23 16:08:58 -07:00
5c6eeac8f0 More coding style cleanup
Fixing more issues found with pylint.  Some that were supposed to
have been fixed in the previous sweep (Ie0db839e) but were missed:

C0321: More than one statement on a single line
W0622: Redefining built-in 'name'

And some more:

W0631: Using possibly undefined loop variable 'name'
W0223: Method 'name' is abstract in class 'name' but is not overridden
W0231: __init__ method from base class 'name' is not called

Change-Id: Ie119183708609d6279e973057a385fde864230c3
2012-10-22 12:30:14 +09:00
e98607248e Support HTTP authentication using user input as fallback
If repo could not find authentication credentials from ~/.netrc, this
patch tries to get user and password from user's console input. This
could be a good choice if user doesn't want to save his plain password
in ~/.netrc or if user doesn't know about the netrc usage.

The user will be prompted only if authentication infomation does not
exist in the password manager. Since main.py firstly loads auth
infomation from ~/.netrc, this will be executed only as fallback
mechanism.

Example:
$ repo upload .
Upload project xxx/ to remote branch master:
 branch yyy ( 1 commit, ...):
 to https://review.zzz.com/gerrit/ (y/N)? y

(repo may try to access to https://review.zzz.com/gerrit/ssh_info and
will get the 401 HTTP Basic Authentication response from server. If no
authentication info in ~/.netrc, This patch will ask username/passwd)

Authorization Required (Message from Web Server)
User: pororo
Password:
....
[OK ] xxx/

Change-Id: Ia348a4609ac40060d9093c7dc8d7c2560020455a
2012-10-12 06:02:35 +09:00
2f6ab7f5b8 Rename "dir" variables
The variable name "dir" conflicts with the name of a Python built-in
function: http://docs.python.org/library/functions.html#dir

Change-Id: I850f3ec8df7563dc85e21f2876fe5e6550ca2d8f
2012-10-10 08:30:15 +02:00
3a6cd4200e Merge "Coding style cleanup" 2012-10-09 14:29:46 -07:00
25f17682ca Merge "Expand ~ to user's home directory for --reference" 2012-10-09 13:46:10 -07:00
8a68ff9605 Coding style cleanup
Fix the following issues reported by pylint:

C0321: More than one statement on a single line
W0622: Redefining built-in 'name'
W0612: Unused variable 'name'
W0613: Unused argument 'name'
W0102: Dangerous default value 'value' as argument
W0105: String statement has no effect

Also fixed a few cases of inconsistent indentation.

Change-Id: Ie0db839e7c57d576cff12d8c055fe87030d00744
2012-10-09 12:45:30 +02:00
297e7c6ee6 Expand ~ to user's home directory for --reference
This allows a user to have a 'repo init' as:
  $ repo init -u ... --reference=~/mirror

Change-Id: Ib85b7c8ffca9d732132c68fe9a8d7f0ab1fa9288
2012-10-08 15:03:20 +02:00
e3b1c45aeb Remove unreachable code
Change 9bb1816b removed part of a block of code, but left the
remaining part unreachable.  Remove it.

Change-Id: Icdc6061d00e6027df32dee9a3bad3999fe7cdcbc
2012-10-05 10:34:19 +02:00
7119f94aba Update commit-msg hook to version from Gerrit v2.5-rc0
Change-Id: I0d11ac0c24cd53386e996b7dd9bd37c89c789f60
2012-10-04 10:31:09 +02:00
01f443d75a Correct call to sys.exit()
It should be `sys.exit()` not `os.exit()`.

Change-Id: Iaeeef456ddf2d17f5df2b712e50e3630bed856c3
2012-10-04 10:31:09 +02:00
b926116a14 Remove ImportError class
The definition of `ImportError` redefines the Python built-in
class of the same name.

It is not used anywhere, so remove it.

Change-Id: I557ce28c93a3306fff72873dc6f477330fc33128
2012-10-04 10:31:09 +02:00
3ff9decfd4 Merge "manifest: record the original revision when in -r mode." 2012-10-03 16:49:12 -07:00
14a6674e32 manifest: record the original revision when in -r mode.
Currently when doing a sync against a revision locked manifest,
sync has no option but to fall back to sync'ing the entire refs space;
it doesn't know which ref to ask for that contains the sha1 it wants.

This sucks if we're in -c mode; thus when we generate a revision
locked manifest, record the originating branch- and try syncing that
branch first.  If the sha1 is found within that branch, this saves
us having to pull down the rest of the repo- a potentially heavy
saving.

If that branch doesn't have the desired sha1, we fallback to sync'ing
everything.

Change-Id: I99a5e44fa1d792dfcada76956a2363187df94cf1
2012-09-28 22:31:27 -07:00
9779565abf Fix incorrect default_groups when parsing projects from XML manifest
Change Details:
* Switch first default group to 'all' instead of 'default'

Change Benefits:
* More consistent with default_groups in the counterpart Save() function
* Fixes bug where command 'repo manifest' added an extra 'default'
  group to every output project element groups attribute. This bug was
  particularly confusing for projects which had 'groups="notdefault"'
  as they were output as 'groups="notdefault,default"' by 'repo manifest'

Change-Id: I5611c027a982d3394899466248b971910bec8c6b
2012-09-26 01:58:48 -04:00
cf76b1bcec sync: Support manual authentication to the manifest server
Add two new command line options, -u/--manifest-server-username and
-p/--manifest-server-password, which can be used to specify a username
and password to authenticate to the manifest server when using the
-s/--smart-sync or -t/--smart-tag option.

If -u and -p are not specified when using the -s or -t option, use
authentication credentials from the .netrc file (if there are any).

Authentication credentials from -u/-p or .netrc are not used if the
manifest server specified in the manifest file already includes
credentials.

Change-Id: I6cf9540d28f6cef64c5694e8928cfe367a71d28d
2012-09-21 11:20:59 -07:00
e00aa6b923 Clean up imports
manifest_xml: import `HEAD` and `R_HEADS` from correct module
version: import `HEAD` from correct module

`HEAD` and `R_HEADS` should be imported from the git_refs module,
where they are originally defined, rather than from the project
module.

repo: remove unused import of readline

cherry_pick: import standard modules on separate lines
smartsync: import subcmd modules explicitly from subcmd

Use:
  `import re
  import sys`
and
  `from subcmds.sync import Sync`

Instead of:
  `import sys, re`
and
  `from sync import Sync`

Change-Id: Ie10dd6832710939634c4f5c86b9ba5a9cd6fc92e
2012-09-18 09:54:57 +02:00
86d973d24e sync: Support authentication to manifest server with .netrc
When using the --smart-sync or --smart-tag option, and the specified
manifest server is hosted on a server that requires authentication,
repo sync fails with the error: HTTP 401 Unauthorized.

Add support for getting the credentials from the .netrc file.

If a .netrc file exists in the user's home directory, and it contains
credentials for the hostname of the manifest server specified in the
manifest, use the credentials to authenticate with the manifest server
using the URL syntax extension for Basic Authentication:

  http://user:password@host:port/path

Credentials from the .netrc file are only used if the manifest server
URL specified in the manifest does not already include credentials.

Change-Id: I06e6586e8849d0cd12fa9746789e8d45d5b1f848
2012-09-11 09:45:48 +02:00
34acdd2534 Fix ManifestParseError when first child node is comment
If the first line of manifest.xml is a XML comment, root.childNodes[0]
is not a 'manifest' element node. The python minidom module will makes
a 'Comment' node as root.childNodes[0]. Since the original code only
checks whether the first child node is 'manifest', it couldn't do any
command including 'sync' due to the 'ManifestParseError' exception. This
patch could allow the comments between '<?xml ...?>' and '<manifest>' in
the manifest.xml file.

Change-Id: I0b81dea4f806965eca90f704c8aa7df49c579402
2012-09-07 08:38:08 -07:00
d94aaef39e sync: Correct imports of R_HEADS and HEAD
`R_HEADS` is imported twice, from both the git_refs and project
modules.

It is actually defined in git_refs, and in project it is imported
from there, so the import of `R_HEADS` from project in the sync
module is redundant.  Remove it.

`HEAD` is imported from project, but like `R_HEADS` it is actually
defined in git_refs.  Import it from git_refs instead.

Change-Id: I8e2b0217d0d9f9f4ee5ef5b8cd0b026174ac52f4
2012-09-07 10:17:00 +02:00
bd489c4eaa sync: catch exceptions when connecting to the manifest server
When connecting to the manifest server, exceptions can occur but
are not caught, resulting in the repo sync exiting with a python
traceback.

Add handling of the following exceptions:

- IOError, which can be raised for example if the manifest server
URL is malformed.
- xmlrpclib.ProtocolError, which can be raised if the connection
to the manifest server fails with HTTP error.
- xmlrpclib.Fault, which can be raised if the RPC call fails for
some other reason.

Change-Id: I3a4830aef0941debadd515aac776a3932e28a943
2012-09-06 11:18:25 -07:00
2dc810c2e4 Fix errors when clone.bundle missing on server
Catch curl failures to download clone.bundle; don't let git try to parse
the 404 page as a bundle file (was causing much user confusion).

This should eliminate false error messages from init and sync such as:
  error: '.repo/manifests.git/clone.bundle' does not look like a v2 bundle file
  fatal: Could not read bundle '.repo/manifests.git/clone.bundle'.
  error: RPC failed; result=22, HTTP code = 400

Signed-off-by: Matt Gumbel <matthew.k.gumbel@intel.com>
Change-Id: I7994f7c0baecfb45bb5a5850c48bd2a0ffabe773
2012-09-06 10:54:46 -07:00
bb1b5f5f86 Allow projects to be specified as notdefault
Instead of every group being in the group "default", every project
is now in the group "all".   A group that should not be downloaded
by default may be added to the group "notdefault".

This allows all group names to be positive (instead of removing groups
directly in the manifest with -default) and offers a clear way of
selecting every project (--groups all).

Change-Id: I99cd70309adb1f8460db3bbc6eff46bdcd22256f
2012-09-05 11:46:48 -07:00
e2126652a3 Make "repo sync -j<count>" stop properly on Ctrl-C.
The threaded 'repo sync' implementation would very often freeze the
process when interrupted by the user with Ctrl-C. The only solution
being to kill -9 the process explicitly from another terminal.

The reason for this is best explained here:

http://snakesthatbite.blogspot.fr/2010/09/cpython-threading-interrupting.html

This patch makes all helper sync threads 'daemon', which allows the
process to terminate immediately on Ctrl-C.

Note that this will forcefully kill all threads in case of interruption; this
is generally a bad thing, but:

  1/ This is equivalent to calling kill -9 in another terminal, which
     is the _only_ thing that can currently stop the process.

  2/ There doesn't seem to be a way to tell the worker threads to
     gently stop when they are in a blocking operation anyway (even
     in the non-threaded case).

+ Do the same for "repo status -j<count>".

Change-Id: Ieaf45b0eacee36f35427f8edafd87415c2aa7be4
2012-09-05 11:38:41 -07:00
9a27d0111d manifest-format.txt: Add documentation for GetManifest RPC method
Add documentation of the GetManifest RPC method in the
manifest-server section.

Change-Id: I5cda5929bc8a0ca9d3f2b9da63216427041d2823
2012-09-05 06:00:47 -07:00
918ff85c1e repo manifest: default to stdout if no "-o"
Change-Id: I1b0ff9ed5df6386f0c2a851c6c48d063199fe663
2012-09-04 09:30:18 -07:00
3d07da82ab init: Improved help text for the --mirror option
Change-Id: Ia6032865f9296b29524c2c25b72bd8e175b30489
2012-08-23 12:15:49 +02:00
e15c65abc2 Remove unused imports
There are several imports that are not used.  Remove them.

Change-Id: I2ac3be66827bd68d3faedcef7d6bbf30ea01d3f2
2012-08-23 12:15:26 +02:00
daa851f6cd manifest-format.txt: Fix a couple of minor spelling mistakes
Change-Id: Ic2d266c8cf08827a71846db9d3711feb02885f01
2012-08-22 09:39:41 -07:00
a43f42f9ff Patches should be submitted to master, not maint
Update SUBMITTING_PATCHES accordingly.

Change-Id: I6fd57a84c67d3762f1f23276d95cac2aeecd5e8f
2012-08-21 14:06:10 +02:00
bb8337fe0f Merge branch 'master' into maint
master's original purpose was to forge ahead on using git submodules,
but this route has been abandoned.

Change-Id: I164a9efc7821bcd1b941ad76649764722046081b
2012-08-14 11:34:34 -07:00
17f85eab24 Omit all default groups when generating a manifest
One of the recent changes introduced implicit path:xxx and name:xxx groups
to every project, however they are not being stripped when generating
a manifest using "repo manifest" command resulting in clutter

Change-Id: Iec8610ba794b2fe4a6cdf0f59ca561595b66f9b5
2012-08-07 11:42:54 -07:00
b9477bc2dd project.py: Replace the relpath function with os.path.relpath
Change-Id: Ib313340344968211cecfc0a718f6072e41da1a91
2012-08-06 23:51:43 +02:00
5e7127d00b Use curl command line tool for clone.bundle
urllib2 is not thread safe and may be causing sync to lock up or
not work correctly on various platforms. Instead use the command
line curl program.

Change-Id: I36eaf18bb4df089d26ea99d533cb015e7c616eb0
2012-08-02 15:18:10 -07:00
5d0efdb14a sync: Honor --no-clone-bundle with -j1
Change-Id: I7c12902e386121a374d525be673092360c67c53d
2012-08-02 12:13:01 -07:00
f35b2d9c31 Fix mirror mode
Change-Id: Ica0e8392562a7ae5aad7e45441c1540e5e2b0238
2012-08-02 11:46:22 -07:00
e0904f721b Fix unsupported operand type(s) for +: 'int' and 'str'
Change-Id: I88455107d63daaa60c3b33c010aa8c730a590c70
2012-08-01 20:44:23 -07:00
9830553748 Fix percent done on resumed /clone.bundle
The Content-Length when resuming is the number of bytes that
remain in the file. To compute the total size as expected by
the progress meter, we must add the bytes already stored.

While we are in this method fix uses of % operator to ensure
a tuple is always supplied.

Change-Id: Ic899231b5bc0ab43b3ddb1d29845f6390e820115
2012-08-01 17:41:26 -07:00
2bc7f5cb3a Fix bug in version_tuple to handle strings with -rc#
Example of version string that caused a problem: git version 1.7.11-rc3

Change-Id: I8a68b6b37f7b2ded23a1f8ae0d12131050a8807b
CC: sop@google.com
2012-07-31 22:18:47 -07:00
b292b98c3e Add remote alias support in manifest
The `alias` is an optional attribute in element `remote`. It can be
used to override attibute `name` to be set as the remote name in each
project's .git/config. Its value can be duplicated while attribute
`name` has to be unique across the manifest file. This helps each
project to be able to have same remote name which actually points
to different remote url.

It eases some automation scripts to be able to checkout/push to same
remote name but actually different remote url, like:

repo forall -c "git checkout -b work same_remote/work"
repo forall -c "git push same_remote work:work"

for example:
The manifest with 'alias' will look like:

<?xml version='1.0' encoding='UTF-8'?>
<manifest>
  <remote alias="same_alias" fetch="git://git.external1.org/" name="ext1"
      review="http://review.external1.org"/>
  <remote alias="same_alias" fetch="git://git.external2.org/" name="ext2"
      review="http://review.external2.org"/>
  <remote alias="same_alias" fetch="ssh://git.internal.com:29418" name="int"
      review="http://review.internal.com"/>
  <default remote="int" revision="int-branch" sync-j="2"/>
  <project name="path/to/project1" path="project1" remote="ext1"/>
  <project name="path/to/project2" path="project2" remote="ext2"/>
  <project name="path/to/project3" path="project3"/>
  ...
</manifest>

In each project, use command "git remote -v"

project1:
same_alias  git://git.external1.org/project1 (fetch)
same_alias  git://git.external1.org/project1 (push)

project2:
same_alias  git://git.external2.org/project2 (fetch)
same_alias  git://git.external2.org/project2 (push)

project3:
same_alias  ssh://git.internal.com:29418/project3 (fetch)
same_alias  ssh://git.internal.com:29418/project3 (push)

Change-Id: I2c48263097ff107f0c978f3e83966ae71d06cb90
2012-07-31 22:13:13 -07:00
2f127de752 Add "repo overview" command.
The overview command shows an overview of each branch in all (or the
specified) projects.  The overview lists any local commits that have
not yet been merged into the project.

The report output is inspired by the report displayed following a
"repo prune" event, with the addition of listing the one-line log
messages for each commit that is not yet merged.

The report can also be filtered to show only active branches; by
default all branches that have commits beyond the upstream HEAD will
be listed.

Change-Id: Ibe67793991ad1aa38de3bc9747de4ba64e5591aa
2012-07-31 22:08:32 -07:00
7da1314e38 Inject the project name into each projects groups.
For CrOS, we have scenarios were people checkout a smaller version
of our manifest via groups, and enable individual repositories as
needed for their work.  Previously this was via local_manifest
manipulation, which breaks via manifest-groups would require a
remove-project tag.

Via injecting the projects name into the projects groups, this
allows us to instead manipulate the configured groups allowing
the user to turn on/off projects as necessary.

Change-Id: I07b7918e16cc9dc28eb47e19a46a04dc4fd0be74
2012-07-31 22:05:44 -07:00
435370c6f0 upload: add --draft option.
Change-Id: I6967ff2f8163cd4116027b3f15ddb36875942af4
2012-07-28 15:44:05 -07:00
e8f75fa368 Don't delete the branch config when switching branches.
The fix for issue #46 in 5d016502eb appears to break syncing in some
situations: the branch is deleted after the point where it's been
configured, which deletes part of its configuration and causes the
config to change each time you call `repo init`, alternating between a
configuration that works and one that doesn't.

Instead of deleting the branch with git branch -D, use git update-ref -d
which just deletes the ref (to avoid the rebase) without touching the
configuration for the branch that was set up during the first repo init.

This appears to ensure the config is left in a valid state all the time
no matter what combination of repo init commands you run, without
reintroducing the rebasing issue.

Change-Id: Iaadaa6e56a46840bbc593fa5b35cb5b34cd3ce69
2012-07-20 15:33:17 +01:00
87636f2ac2 Fix for failures with repo upload for projects that have a SHA1 for a revision; instead use the default manifest revision
Change-Id: Ie5ef5a45ed6b0ca1a52a550df3cd7bd72e745f5f
2012-06-14 16:54:32 -07:00
337aee0a9c Single quote http.proxy in GIT_CONFIG_PARAMETERS
Git requires the values in this environment variable to be
single quoted. repo must wrap the expression into '' before
adding it to the environment.

Change-Id: I20a1fb8772f9aa6e9fd5a0516c981c2ca020ef05
2012-06-13 10:42:16 -07:00
7cf1b36bcd Detach branch even when already on the latest revision using sync -d
This patch fixes repo behaviour when running sync -d with unmodified
topic branches.

Prior to this patch sync -d would see the latest revision is already
checked out, thus staying on the branch. Since "-d" means detach we
should follow git's behaviour and actually detach from the branch in
that case.

Basic test case - after a fresh repo init + sync -
        * repo start --all testdetach
        * repo sync -d
        * repo status
-> status shows active topic branch "testdetach",
   should show :
nothing to commit (working directory clean)

Change-Id: Ic1351e6b5721b76557a51ab09f9dd42c38a4b415
2012-06-13 10:36:17 -07:00
5e57234ec6 Support automatically stashing local modifications during repo-rebase.
Currently repo-rebase requires that all modifications be committed
locally before it will allow the rebase. In high-velocity environments,
you may want to just pull in newer code without explicitly creating
local commits, which is typically achieved using git-stash.

If called with the --auto-stash command line argument, and it is
determined that the current index is dirty, the local modifications
are stashed, and the rebase continues.  If a stash was performed, that
stash is popped once the rebase completes.

Note that there is still a possibility that the git-stash pop will
result in a merge conflict.

Change-Id: Ibe3da96f0b4486cb7ce8d040639187e26501f6af
2012-06-13 10:34:41 -07:00
5d016502eb Fix switching manifest branches using repo init -b
See repo issue #46 :
	https://code.google.com/p/git-repo/issues/detail?id=46

When using repo init -b on an already existing repository,
the next sync will try to rebase changes coming from the old manifest
branch onto the new, leading in the best case scenario to conflicts
and in the worst case scenario to an incorrect "mixed up" manifest.

This patch fixes this by deleting the "default" branch in the local
manifest repository when the -d init switch is used, thus forcing
repo to perform a fresh checkout of the new manifest branch

Change-Id: I379e4875ec5357d8614d1197b6afbe58f9606751
2012-06-13 10:00:57 -07:00
475a47d531 Restore include support.
Calculation of where the include file lives was broken by 23acdd3f14
since it resulted in looking for the first include in .repo, rather
than .repo/manifests.

While people can work around it via setting their includes to
manifests/<include-target>, that breaks down since each layer of
includes would then have to be relative.

As such, restore the behaviour back to 2644874d; manifests includes
are calculated relative to the manifest root (ie, .repo/manifests);
local manifests includes are calculated relative to .repo/ .

Change-Id: I74c19ba614c41d2f08cd3e9fd094f3c510e3bfd1
2012-06-07 20:19:04 -07:00
62d0b10a7b Use GIT_CONFIG_PARAMETERS instead of -c for http.proxy
Ancient versions of Git don't understand the -c command line flag
that we tried to use to pass http_proxy down into Git on Darwin.
Use the environment variable instead, to more gracefully degrade
with these old versions.

Change-Id: Iffffa32088c1fd803895b990b3377ecfec6a1b14
2012-06-05 15:11:15 -07:00
d666e93ecc repo: Add option review.URL.uploadtopic support
This patch adds the option to include topic branches by adding the
following to a .gitconfig file:

    uploadtopic = true

This option is only read in when the -t option is not already
specified at the command line.

Change-Id: I0e0eea49438bb4e4a21c2ac5bd498b68b5a9a845
2012-06-05 08:01:29 -07:00
3f61950f01 Use gerrit.googlesource.com/git-repo as the default URL
This is basically the same repository, but may be slightly more
up-to-date than the one on code.google.com/p/git-repo.

Change-Id: I5c99539f53231958eefb6993f00997c9adf0a3c9
2012-06-05 07:57:24 -07:00
4fd38ecc3a Detect git is not installed
Fix detection for Git not being in $PATH during the initial
run of `repo init` in a new directory.

Change-Id: I2b1fcce1fb8afc47271f5c3bd2a28369009b2fb7
2012-06-05 07:56:09 -07:00
9fae805e04 Pass http_proxy as -c http.proxy on Mac OS X
The system libcurl library seems to ignore http_proxy on Mac OS
X systems. Copy the http_proxy environment variable (if set) as
`git -c http.proxy` whenever running a Git command.

Change-Id: I0ab29336897178f70b85092601f9fcc306dd17e1
2012-05-25 08:21:37 -07:00
6a927c5d19 hooks/pre-auto-gc: look in sysfs to see if a battery is known.
Barring any kernel bugs, if this directory exists and there is
a symlink in there (which will point to the battery object),
that means there is a battery known to the kernel.

No symlink should mean no battery as far as the kernel is concerned.

Change-Id: Ib12819a5bbb816f0ae5ca080e5812a2db08441e9
2012-05-25 02:25:59 -07:00
eca119e5d6 Allow projects with groups=None
Mirror manifest and repo projects are outside the manifest and
have no groups.  Allow project groups to be None for these
projects.

Change-Id: I3e1c4add894fe1c43aa4e77a1fc1558aa10dd191
2012-05-24 15:40:05 -07:00
6ba6ba0ef3 Fix initial sync broken by sync-c option
Change-Id: I308753da8944e6ce5c46e3bfee1bcd41d5b7e292
2012-05-24 09:46:50 -07:00
23acdd3f14 Parse manifest and local_manifest together
Combine manifest and local_manifest into a single list of elements
before parsing.  This will allow elements in the local_manifest to
affect elements in the main manifest.

Change-Id: I4d34c9260b299a76be2960b07c0c3fe1af35f33c
2012-05-24 09:32:15 -07:00
2644874d9d ManifestXml: add include support
Having the ability to include other manifests is a very practical feature
to ease the managment of manifest. It allows to divide a manifest into separate
files, and create different environment depending  on what we want to release

You can have unlimited recursion of include, the manifest configs will simply be concatenated
as if it was in a single file.

command "repo manifest" will create a single manifest, and not recreate the manifest hierarchy

for example:
Our developement manifest will look like:

<?xml version='1.0' encoding='UTF-8'?>
<manifest>
  <default revision="platform/android/main" remote="intel"/>
  <include name="server.xml"/> <!-- The Server configuration -->
  <include name="aosp.xml" />  <!-- All the AOSP projects -->
  <include name="bsp.xml" />   <!-- The BSP projects that we release in source form -->
  <include name="bsp-priv.xml" /> <!-- The source of the BSP projects we release in binary form -->
</manifest>

Our release manifest will look like:

<?xml version='1.0' encoding='UTF-8'?>
<manifest>
  <default revision="platform/android/release-ext" remote="intel"/>
  <include name="server.xml"/> <!-- The Server configuration -->
  <include name="aosp.xml" />  <!-- All the AOSP projects -->
  <include name="bsp.xml" />   <!-- The BSP projects that we release in source form -->
  <include name="bsp-ext.xml" /> <!-- The PREBUILT version of the BSP projects we release in binary form -->
</manifest>

And it is also easy to create and maintain feature branch with a manifest that looks like:

<?xml version='1.0' encoding='UTF-8'?>
<manifest>
  <default revision="feature_branch_foobar" remote="intel"/>
  <include name="server.xml"/> <!-- The Server configuration -->
  <include name="aosp.xml" />  <!-- All the AOSP projects -->
  <include name="bsp.xml" />   <!-- The BSP projects that we release in source form -->
  <include name="bsp-priv.xml" /> <!-- The source of the BSP projects we release in binary form -->
</manifest>

Signed-off-by: Brian Harring <brian.harring@intel.com>
Signed-off-by: Pierre Tardy <pierre.tardy@intel.com>
Change-Id: I833a30d303039e485888768e6b81561b7665e89d
2012-05-24 09:07:24 -07:00
3d125940f6 repo download: add --ff-only option
Allows to ff-only a gerrit patch
This patch is necessary to automatically ensure that the patch will
be correctly submitted on ff-only gerrit projects

You can now use:
repo download (--ff-only|-f) project changeid/patchnumber

This is useful to automate verification of fast forward status of a patch
in the context of build automation, and commit gating (e.g. buildbot)

Change-Id: I403a667557a105411a633e62c8eec23d93724b43
Signed-off-by: Erwan Mahe <erwan.mahe@intel.com>
Signed-off-by: Pierre Tardy <pierre.tardy@intel.com>
2012-05-24 09:04:20 -07:00
a94f162b9f repo download: add --revert option
BZ: 4779
Allows to revert a gerrit patch
This patch is necessary for the on-demand creation of
engineering builds using buildbot

You can now use:
repo download [--revert|-r project changeid/patchnumber

This is useful to automate reverting of a patch
in the context of build automation, and regression bisection

Change-Id: I3985e80e4b2a230f83526191ea1379765a54bdcf
Signed-off-by: Erwan Mahe <erwan.mahe@intel.com>
Signed-off-by: Pierre Tardy <pierre.tardy@intel.com>
2012-05-24 09:03:10 -07:00
e5a2122e64 repo download: add --cherry-pick option
default option uses git checkout, and thus overwrite the previous
checkouts.  this is a problem for automated builds of several
changesets in the same project for daily builds of pending submission

You can now use:
repo download [--cherry-pick|-c] project changeid/patchnumber

This will parse the manifest, cd to the corresponding project
download the changes to FETCH_HEAD and cherry-pick the result.

This is useful to automate cherry-picking of a patch
in the context of build automation, and commit gating (e.g. buildbot)

Change-Id: Ib638afd87677f1be197afb7b0f73c70fb98909fe
Signed-off-by: Pierre Tardy <pierre.tardy@intel.com>
2012-05-24 09:02:38 -07:00
ccf86432b3 Avoid failing concat for multi-encoding filenames
repo status should output filenames one by one instead of trying to
build a string from incompatible encodings (like utf-8 and sjis
filenames)

Change-Id: I52282236ececa562f109f9ea4b2e971d2b4bc045
2012-05-24 08:58:10 -07:00
79770d269e Add sync-c option to manifest
There are use-cases when fetching all branch is impractical and
we really need to fetch only one branch/tag.
e.g. there is a large project with binaries and every update of a
binary file is put to a separate branch.
The whole project history might be too large to allow users fetch it.

Add 'sync-c' option to 'project' and 'default' tags to make it possible
to configure 'sync-c' behavior at per-project and per-manifest level.

Note that currently there is no possibility to revert boolean flag from
command line. If 'sync-c' is set in manifest then you cannot make
full fetch by providing a repo tool argument.

Change-Id: Ie36fe5737304930493740370239403986590f593
2012-04-23 14:10:52 -07:00
c39864f5e1 Treat groups= as default
Previous incarnations of groups support left "groups=" in the
repo .config, which is now treated as "delete all the projects".
Treat empty groups configuration the same as no groups
configuration.

Change-Id: I57dab8dac55bdbf4cc181e2748cd2e4e510764f5
2012-04-23 13:43:41 -07:00
5465727e53 Fix syntax errors in subcmds/init.py
Fixes three errors:
Python doesn't like the line wrap after 'and'.
platform.system is a function, needs to be platform.system().
Typo all_platfroms instead of all_platforms.

Change-Id: Ia875e521bc01ae2eb321ec62d839173c00f86c2d
2012-04-23 13:43:41 -07:00
d21720db31 Add a --platform flag
Projects may optionally specify their platform
(eg, groups="platform-linux" in the manifest).

By default, repo will automatically detect the platform. However,
users may specify --platform=[auto|all|linux|darwin].

Change-Id: Ie678851fb2fec5b0938aede01f16c53138a16537
2012-04-23 12:50:00 -07:00
971de8ea7b Refine groups functionality
Every project is in group "default".  "-default" does not remove
it from this project.  All group names specified in the manifest
are positive names as opposed to a mix of negative and positive.

Specified groups are resolved in order.  If init is supplied with
--groups="group1,-group2", the following describes the project
selection when syncing:

  * all projects in "group1" will be added, and
  * all projects in "group2" will be removed.

Change-Id: I1df3dcdb64bbd4cd80d675f9b2d3becbf721f661
2012-04-23 12:39:05 -07:00
24c1308840 Add project annotation handling to repo
Allow the optional addition of "annotation" nodes nested under
projects.  Each annotation node must have "name" and "value"
attributes.  These name/value pairs will be exported into the
environment during any forall command, prefixed with "REPO__"

In addition, an optional "keep" attribute with case insensitive "true"
or "false" values can be included to determine whether the annotation
will be exported with 'repo manifest'

Change-Id: Icd7540afaae02c958f769ce3d25661aa721a9de8
Signed-off-by: James W. Mills <jameswmills@gmail.com>
2012-04-23 12:35:08 -07:00
b962a1f5e0 Check if SHA1 presents in repository
Previously repo had incorrect code that did not really check
if sha1 presents in a project. It worked for tags though.

Check if a revision (either tag or sha1) is present by using
'git rev_parse' functionality.

Change-Id: I1787f3348573948573948753987394839487572b
2012-04-23 11:09:17 -07:00
5acde75e5d Add manifest groups
Allows specifying a list of groups with a -g argument to repo init.
The groups act on a group= attribute specified on projects in the
manifest.
All projects are implicitly labelled with "default" unless they are
explicitly labelled "-default".
Prefixing a group with "-" removes matching projects from the list
of projects to sync.
If any non-inverted manifest groups are specified, the default label
is ignored.

Change-Id: I3a0dd7a93a8a1756205de1d03eee8c00906af0e5
Reviewed-on: https://gerrit-review.googlesource.com/34570
Reviewed-by: Shawn Pearce <sop@google.com>
Tested-by: Shawn Pearce <sop@google.com>
2012-04-13 09:46:00 -07:00
d67872d2f4 Option for 'repo diff' to generate output suitable for 'patch' cmd
The -u option causes 'repo diff' to generate diff output
with file paths relative to the repository root,
so the output can be applied to the Unix 'patch' command.
The name '-u' was selected for convenience, because
both 'diff' and 'git diff' accept the option with the same name
to generate an 'unified diff' output suitable for 'patch' command.

Change-Id: I79c8356db4ed20ecaccc258b3ba139db76666fe0
Reviewed-on: https://gerrit-review.googlesource.com/34380
Reviewed-by: Shawn Pearce <sop@google.com>
Tested-by: Shawn Pearce <sop@google.com>
2012-04-13 09:20:10 -07:00
e9d6b611c5 New flag for repo upload: --current_branch (--cbr)
A convenient equivalent to `repo upload --br=<current git branch>`.

Note that the head branch will be selected for each project
uploaded by repo, so different branches may be uploaded for
different projects.

Change-Id: I10ad8ceaa63f055105c2d847c6e329fa4226dbaf
2012-04-06 10:43:36 -04:00
c3d2f2b76f Ignore /clone.bundle on HTTP 401, 403 and 404
401: Unauthorized, authentication may be required. This is usually
     handled internally by the HTTP client in Python. If it reaches
     our code in repo, the Python HTTP client didn't find a password
     in ~/.netrc that it could use.

403: Authentication was supplied, but is incorrect. It might be
     that the CDN doesn't want to offer this clone.bundle file
     to the client, but the Git fetch operation would still be
     successful. This might arise if branch level read controls
     were used in Gerrit Code Review and the /clone.bundle file
     contained branches not visible to the client.

404: The server has no /clone.bundle file available.

In all of these cases, sliently ignore the /clone.bundle file HTTP
error and let the Git operation take over.

Change-Id: I1787f3cac86b017351952bbb793fe5874d83c72b
2012-03-22 14:18:40 -07:00
cd7c5deca0 Do not change branch.foo.merge in case of manifest sync
In case of manifest/smart sync repo changes ".merge" config
option from branch to SHA. Doing 'repo upload' fails as
repo tries to upload to a remote branch that looks like SHA
(e.g. refs/for/23423423423423423423423)

Do not update the .merge in case if revision is SHA.

Change-Id: I9139708fa17f21eec5a7e23c3255333626bf529e
2012-03-20 14:11:56 -07:00
e02ac0af2e sync: --no-clone-bundle disables the clone bundle support
Change-Id: Ia9ed7da8451b273c1be620c3dd0dcad777b29096
2012-03-14 15:38:28 -07:00
898e12a2d9 Permit - in URL schemes for special URLs
Clients might be using their own special git-remote-* helper that
has a hypen in its name. Permit - in the scheme part of the URL
when trying to decide if it is an SSH URL and assume it is *not*
SSH if the URL matches "foo-bar://" style.

Change-Id: I7ba2d810a614f6e605a441d5972902c4a14e73fd
2012-03-14 15:28:22 -07:00
ae0a36c9a5 Add support for Apache Digest authentication for repo init.
repo tool supports only Basic authentication for now. For those
who want to use this tool to manage their own projects, in case
the administrator has configured the Apache server with Digest
authentication method, users will fail to be authenticated when
they run the command 'repo init'.
Add the digest authentication password manager to the handler
list will fix this issue.

Since Git HTTP protocol will require the user be authenticated
for fetch operation first before pushing commits to the remote,
it is unlikely for the administrator to implement anonymous
read (aka pull) access and write access (aka push) for
authenticated user. Both read and write have to be authenticated.
Be aware that the user may have to add an extra line in his
~/.netrc file:
-------------------
account example.com
-------------------
where 'example.com' is the realm for Apache Digest authentication.

Change-Id: I76eb27b205554426d9ce1965deaaf727b87916cd
Signed-off-by: Xiaodong Xu <stid.smth@gmail.com>
2012-03-14 15:01:34 -07:00
76abcc1d1e repo status to print project name on clean gits
repo status just prints "# on branch oprofile" if you have branched
in clean status. This doesn't really tell which branch is meant.

Instead we can use the same syntax with modified gits which will
give us detailed information.

Change-Id: I55fe5154d278e10a814281dd2ba501ec6e956730
2012-03-12 12:25:40 -07:00
d315382572 Add 'rebase="false"' attribute to the <project/> XML.
This new attribute can prevent 'repo sync' from automatically rebasing.

I hit a situation in where one of the git repositories I was tracking
was actually an external repository that I wanted to pull commits
into and merge myself. (NOT rebase, since that would lose the merge
history.) In this case, I'm not using 'repo upload', I'm manually
managing the merges to and from this repository.

Everything was going great until I typed 'repo sync' and it rebased
my manually-merged tree. Hence the option to skip it.

Change-Id: I965e0dd1acb87f4a56752ebedc7e2de1c502dbf8
2012-03-12 12:24:22 -07:00
43bda84362 Avoid missing content-length header in project.py
Occassionally, the content-length may be missing when using urlib
in python 2.6 and 2.7.  This change assumes the value of the header is
0 if it doesn't exist

Change-Id: Iaf1c8a796bc667823d4d7c30f9b617644b271d00
2012-03-12 12:13:15 -07:00
9b017dab46 Update SUBMITTING_PATCHES
The review server is now at gerrit-review.googlesource.com.

Change-Id: I4be67fdb1876eb2e2af4420ac63557596b9e233b
2012-02-28 18:54:33 -08:00
e9dc3b3368 sync: Add manifest_name parameter
This parameter changes the manifest used by 'repo sync' for only
this execution. It should be useful for developers wishing to get
the repo temporarily into a known state, without clobbering their
existing manifest.

Tested by shifting Chrome OS between minilayout and full, and
between several release-builder-generated manifests.

Change-Id: I14194b665195b0e78f368d9ec8b8a83227af2627
2012-01-26 12:32:36 -05:00
c9571423f8 upload: Support uploading to Gerrit over https://
If SSH is not available, Gerrit returns NOT_AVAILABLE to the /ssh_info
query made by repo upload. In this case fallback to the /p/$PROJECT URL
that Gerrit also exports and use that for uploads.

Change-Id: I1e3e39ab709ecc0a692614a41a42446426f39c08
2012-01-11 16:18:40 -08:00
34fb20f67c Revert "Default repo manifest settings in git config"
This reverts commit ee1c2f5717.

This breaks a lot of buildbot systems. Rolling it back for now
until we can understand what the breakage was and how to fix it.
2011-11-30 13:41:02 -08:00
ecff4f17b0 Describe the repo launch version in repo version
repo version v1.7.8
         (from https://android.googlesource.com/tools/repo.git)
  repo launcher version 1.14
         (from /home/sop/bin/repo)
  git version 1.7.8.rc2.256.gcc761
  Python 2.6.5 (r265:79063, Apr 16 2010, 13:57:41)
  [GCC 4.4.3]

Change-Id: Ifcbe5b0e226a1a6ca85455eb62e4da5e9a0f0ca0
2011-11-29 15:02:15 -08:00
cc14fa9820 Improve error handling when reading loose refs
When repo is trying to figure out branches the repository has by
traversing refs/heads, add exception handling for readline.

Change-Id: If3b2a3720c6496f52f629aa9a2539f186d6ec882
2011-11-29 14:43:04 -08:00
3ce2a6b46b Propagate result codes from subcmds to sys.exit().
Allows scripts driving repo to know when git failures have
occurred, not just repo internal errors.

Change-Id: Id20fbbb405c35a148e72c87b822da3f3bf93839c
2011-11-29 14:38:19 -08:00
841be34968 Don't prompt the user for name/email unless necessary
If the user has already configured a workspace, use these values
when re-running 'repo init'.

Otherwise, if the user has global name and e-mail set, use these.

It's always possible to override this and be prompted by specifying
--config-name when running 'repo init'.

Change-Id: If45f0e4b14884071439fb02709dc5cb53f070f60
2011-11-29 14:31:56 -08:00
ee1c2f5717 Default repo manifest settings in git config
A default manifest URL can be specified using:
  git config --global repo-manifest.<id>.url <url>

A default manifest server can be specified using:
  git config --global repo-manifest.<id>.server <url>

A default git mirror reference can be specified using:
  git config --global repo-manifest.<id>.reference <path>

This will allow the user to use 'repo init -u <id>' as
a shorter alternative to specifying the full URL.

Also, manifest server will not have to be specified in the
manifest XML and the reference will not have to be specified
on the command line. If they are, they will override these
default values however.

Change-Id: Ifdbc160bd5909ec7df9efb0c5d7136f1d9351754
Signed-off-by: Victor Boivie <victor.boivie@sonyericsson.com>
2011-11-29 14:24:58 -08:00
6a1f737380 Added remote destination branch information when uploading.
Several times one have done an upload only to later notice in gerrit
that the upload was done to the wrong branch as the git has not yet
been branched for the current git. This change will make repo print
what the destination branch is when asking the user if she wants to
go through with the upload.

Change-Id: Ia9c3a92a6a04c022edfebf4f8d651ac062bb1f3b
2011-11-29 14:01:57 -08:00
e9311273dd repo: capitalize default prompt char
It is common in command line tools to indicate what the default answer
will be if the user simply hits enter.  In repo, the display is just
"y/n" with no indication as to which is the default.  So change the n
to N in the messages since that is how repo operates.

Change-Id: I81819ae630355072eb0365e59168b0921289498f
2011-11-29 12:38:52 -08:00
605a9a487b Fixed UnicodeDecodeError while uploading changes.
When commit with comment that has non-ASCII characters,
UnicodeDecodeError will be raised
while uploading multiple project/branch changes.
Because some strings in script are not str type, but unicode.
So all the strings are decoded to unicode,
and python use ascii to do this,
it can not decode non-ASCII characters,
so UnicodeDecodeError raised.

Signed-off-by: chenguodong <chenguodong@huawei.com>

Change-Id: I46447f489a4b9760a5899c7ba9d764b688594e46
2011-11-29 12:11:41 -08:00
2a32f6afa6 Fix typo
Change-Id: Idd68ad0a34fcf4bd4e18b0248f50187a539d610a
2011-11-29 12:09:35 -08:00
498fe90b45 Stabilize repo communication with subprocesses.
Make repo use the standard way in python to work with pipes.
Communication via pipes to sub processes is done by calling
communicate(). This will make repo not hang every now and
then.

Change-Id: Ibe2c4ecbdbcbe72f0b725ca50d54088e5646fc5d
2011-11-29 11:54:58 -08:00
53d6f4d17e Add a sync flag that fetches only current branch
There is also shortcuts in case if the "current branch" is
a persistent revision such as tag or sha1. We check if the
persistent revision is present locally and if it does - do
no fetch anything from the server.

This greately reduces sync time and size of the on-disk repo

Change-Id: I23c6d95185474ed6e1a03c836a47f489953b99be
2011-11-03 13:08:27 -07:00
9d8f914fe8 Remove extra '/' in RemoteSpec
urljoin appends a '/' if only the domain is in the url path.  This
change strips that off before creating a RemoteSpec
2011-11-03 13:05:14 -07:00
ceea368e88 Correctly name projects when mirroring
A bug introduced by relative urls caused projects such as manifest.git
to be placed in the root directory instead of the directory they should
by in.

This fix creates and refers to a resolvedFetchUrl in the _XmlRemote
class in order to get a fetchUrl that is never relative.
2011-10-20 11:01:38 -07:00
b660539c4a Fix sync on Python 2.6.6
Python 2.6.6 has the same bug as Python 2.7, where HTTP
authentication just stops working, but does not have the
setter method to clear the retry counter. Work around by
setting the field directly if it exists.

Change-Id: I6a742e606bb7750dc66c33fc7c5d1310541db2c8
Signed-off-by: Shawn O. Pearce <sop@google.com>
2011-10-11 15:58:07 -07:00
752371d91b help: Fix help sync
help sync crashed as sync required the manifest to be configured to
create the option parser, as the default number of jobs is required.

Change-Id: Ie75e8d75ac0e38313e4aab451cbb24430e84def5
Signed-off-by: Shawn O. Pearce <sop@google.com>
2011-10-11 15:23:41 -07:00
1a68dc58eb upload: Honor REPO_HOST_PORT_INFO environment variable
REPO_HOST_PORT_INFO can be set to 'host:port' and be used
instead of the review URL given in the manifest.

Change-Id: I440bdecb2c2249fe5285ec5d0c28a937b4053450
Signed-off-by: Shawn O. Pearce <sop@google.com>
2011-10-11 14:12:46 -07:00
df5ee52050 Fix Python 2.4 support
Change-Id: I89521ae52fa564f0d849cc51e71fee65b3c47bab
Signed-off-by: Shawn O. Pearce <sop@google.com>
2011-10-11 14:06:11 -07:00
fab96c68e3 Work around Python 2.7 urllib2 bug
If the remote is using authenticated HTTP, but does not have
$GIT_URL/clone.bundle files in each repository, an initial sync
would fail around 8 projects in due to the library not resetting
the number of failures after getting a 404.

Work around this by updating the retry counter ourselves.

The urllib2 library is also not thread-safe. Make it somewhat
safer by wrapping the critical section with a lock.

Change-Id: I886e2750ef4793cbe2150c3b5396eb9f10974f7f
Signed-off-by: Shawn O. Pearce <sop@google.com>
2011-10-11 12:18:07 -07:00
bf1fbb20ab Fix AttributeError: 'HTTPError' object has no attribute 'reason'
Not every version of urllib2 supplies a reason object on the
HTTPError exception that it throws from urlopen().  Work around
this by using str(e) instead and hope the string formatting includes
sufficient information.

Change-Id: I0f4586dba0aa7152691b2371627c951f91fdfc8d
Signed-off-by: Shawn O. Pearce <sop@google.com>
2011-10-11 09:31:58 -07:00
29472463ba Work around Python 2.7 failure to initialize base class
urllib2 returns a malformed HTTPError object in certain situations.
For example, urllib2 has a couple of places where it creates an
HTTPError object with no fp:

  if self.retried > 5:
    # retry sending the username:password 5 times before failing.
    raise HTTPError(req.get_full_url(), 401, "basic auth failed",
                    headers, None)

When it does that, HTTPError's ctor doesn't call through to
addinfourl's ctor:

  # The addinfourl classes depend on fp being a valid file
  # object.  In some cases, the HTTPError may not have a valid
  # file object.  If this happens, the simplest workaround is to
  # not initialize the base classes.
  if fp is not None:
    self.__super_init(fp, hdrs, url, code)

Which means the 'headers' slot in addinfourl is not initialized and
info() fails.  It is completely insane that urllib2 decides not to
initialize its own base class sometimes.

Change-Id: I32a0d738f71bdd7d38d86078b71d9001e26f1ec3
Signed-off-by: Shawn O. Pearce <sop@google.com>
2011-10-11 09:24:07 -07:00
c325dc35f6 sync: Fetch after applying bundle and retry after errors
After a $GIT_URL/clone.bundle has been applied to the new local
repository, perform an incremental fetch using `git fetch` to ensure
the local repository is up-to-date. This allows the hosting server
to offer stale /clone.bundle files to bootstrap a new client.

If a single git fetch fails, it may succeed again after a short
delay.  Transient failures are typical in environments where the
remote Git server happens to have limits on how many requests it
can serve at once (the anonymous git daemon, or an HTTP server).
Wait a randomized delay between 30 and 45 seconds and retry the
failed project once.  This delay gives the site time to recover
from a transient traffic spike, and the randomization makes it less
likely that a spike occurs again from all of the same clients.

Change-Id: I97fb0fcb33630fb78ac1a21d1a4a3e2268ab60c0
Signed-off-by: Shawn O. Pearce <sop@google.com>
2011-10-03 08:30:24 -07:00
f322b9abb4 sync: Support downloading bundle to initialize repository
An HTTP (or HTTPS) based remote server may now offer a 'clone.bundle'
file in each repository's Git directory. Over an http:// or https://
remote repo will first ask for '$URL/clone.bundle', and if present
download this to bootstrap the local client, rather than relying
on the native Git transport to initialize the new repository.

Bundles may be hosted elsewhere. The client automatically follows a
HTTP 302 redirect to acquire the bundle file. This allows servers
to direct clients to cached copies residing on content delivery
networks, where the bundle may be closer to the end-user.

Bundle downloads are resumeable from where they last left off,
allowing clients to initialize large repositories even when the
connection gets interrupted.

If a bundle does not exist for a repository (a HTTP 404 response
code is returned for '$URL/clone.bundle'), the native Git transport
is used instead. If the client is performing a shallow sync, the
bundle transport is not used, as there is no way to embed shallow
data into the bundle.

Change-Id: I05dad17792fd6fd20635a0f71589566e557cc743
Signed-off-by: Shawn O. Pearce <sop@google.com>
2011-09-28 10:07:36 -07:00
db728cd866 Allow remote url to be relative to manifst url 2011-09-28 10:07:01 -07:00
c4657969eb sync: Update default -j flag from manifest
If the manifest is updated and the default sync-j attribute
was modified, honor it during this sync session if the user
has not supplied a -j flag on the command line.

Change-Id: I127ee5c779e2bbbb40b30bddc10ec1fa704b3bf3
Signed-off-by: Shawn O. Pearce <sop@google.com>
2011-09-26 09:08:44 -07:00
7b947de1ee Ignore missing ~/.netrc
Change-Id: Ifa6065d57a6cb11ad57ddd44bc88d9690fe234ab
Signed-off-by: Shawn O. Pearce <sop@google.com>
2011-09-23 11:50:31 -07:00
6392c87945 sync: Allow -j to have a default in manifest
This permits manifest authors to suggest a number of parallel
fetch operations against a remote server. For example, Gerrit
Code Review servers support queuing of requests and processes
them in first-in, first-out order. Running concurrent fetches
can utilize multiple CPUs on the Gerrit server, but will also
decrease overall operation latency by having the request put
into the queue ready to execute as soon as a CPU is free.

Change-Id: I3d3904acb6f63516bae4b071c510ad57a2afab18
Signed-off-by: Shawn O. Pearce <sop@google.com>
2011-09-22 18:08:27 -07:00
97d2b2f7a0 sync: Limit -j to file descriptors
Each worker thread requires at least 3 file descriptors to run the
forked 'git fetch' child to operate against the local repository.
Mac OS X has the RLIMIT_NOFILE set to 256 by default, which means
a sync -j128 often fails when the workers run out of pipes within
the Python parent process.

Change-Id: I2cdb14621b899424b079daf7969bc8c16b85b903
Signed-off-by: Shawn O. Pearce <sop@google.com>
2011-09-22 18:08:26 -07:00
3a0e782790 Add global option --time to track execution
This prints a simple line after a command ends, providing
information about how long it executed for using real wall
clock time. Its mostly useful for looking at sync times.

Change-Id: Ie0997df0a0f90150270835d94b58a01a10bc3956
Signed-off-by: Shawn O. Pearce <sop@google.com>
2011-09-22 18:08:18 -07:00
490d09a314 Support units in progress messages
This allows our progress meter to be used for bytes transferred, by
setting the units to KB or MB to let the user know the size.

Change-Id: Ie8653d4a40d79439026c18bd51915845b2c5bba9
Signed-off-by: Shawn O. Pearce <sop@google.com>
2011-09-19 14:52:57 -07:00
13111b4e97 Add support for url.*.insteadof
Teach repo how to resolve URLs using the url.insteadof feature
that C Git natively uses during clone, fetch or push. This will
later allow repo to resolve a URL before accessing it directly.
We do not want to pre-resolve things and store the resolved URL
into individual projects, as this makes it impossible for the
user to undo the insteadof mapping at a later date.

Change-Id: I0f62e811197c53fbc8a8be424e3cabf4ed07b4cb
Signed-off-by: Shawn O. Pearce <sop@google.com>
2011-09-19 14:52:57 -07:00
bd0312a484 Support ~/.netrc for HTTP Basic authentication
If repo tries to access a URL over HTTP and the user needs to
authenticate, offer a match from ~/.netrc. This matches behavior
with the Git command line client.

Change-Id: I803f3c5d562177ea0330941350cff3cc1e1bef08
Signed-off-by: Shawn O. Pearce <sop@google.com>
2011-09-19 14:52:32 -07:00
334851e4b6 Enhance HTTP support
Setting REPO_CURL_VERBOSE=1 in the environment will register a debug
level HTTPHandler on the urllib2 library, showing HTTP requests and
responses on the stderr channel of repo.

During any HTTP or HTTPS request created inside of the repo process,
a custom User-Agent header is now defined:

  User-Agent: git-repo/1.7.5 (Linux) git/1.7.7 Python/2.6.5

Change-Id: Ia5026fb1e1500659bd2af27416d85e205048bf26
Signed-off-by: Shawn O. Pearce <sop@google.com>
2011-09-19 14:51:47 -07:00
014d060989 Honor http_proxy variable globally
If the http_proxy environment variable was set, honor it during
the entire repo session for any Python created HTTP connections.

Change-Id: Ib4ae833cb2cdd47ab0126949f6b399d2c142887d
Signed-off-by: Shawn O. Pearce <sop@google.com>
2011-09-11 13:11:04 -07:00
44da16e8a0 Change default REPO_URL to code.google.com
Change-Id: If7700daf96fb8f3ee449e5774017272ef31b4b44
2011-09-05 14:16:49 -07:00
65e0f35fda Add commit-msg hook also for manifest project
The manifest project has - by design - not a review URL associated
with it. It is actually not even a 'project' in repo's sense.

This will prevent the commit-msg hook from being added, which is
not necessarily wanted as the project is managed in gerrit.

This commit will enable the commit-msg hook, which in turn will
add the Change-Id-line to every new commit in it. This simplifies
replacing patch sets (by git push ... refs/for/...).

Change-Id: I42d0f6fd79e6282d9d47074a3819e68d968999a7
Signed-off-by: Victor Boivie <victor.boivie@sonyericsson.com>
2011-07-20 07:34:23 -07:00
08c880db18 Smart tag support
This is an evolution of 'smart-sync' that adds a new option, -t,
that allows you to specify a tag/label to use instead of the
"latest good build" on the current manifest branch which -s does.

Signed-off-by: Victor Boivie <victor.boivie@sonyericsson.com>
Change-Id: I8c20fd91104a6aafa0271d4d33f6c4850aade17e
2011-07-20 07:13:48 -07:00
a101f1c167 Honor 'http_proxy' environment variable
'repo upload' makes http request using urllib2 python library.
Unfortunately this library does not work (by default) in case
if the user behind a proxy.

This change adds proxy handler in case if 'http_proxy' environment
variable is set.

Change-Id: Ic4176ad733fc21bd5b59661b3eacc2f0a7c3c1ff
2011-07-20 07:11:00 -07:00
49cd59bc86 Add --depth option to main repo wrapper.
See related repo change:
  https://review.source.android.com/#change,22722

Change-Id: I9bdd86971c94604477b91cdf47d6fac2c0bc186e
2011-06-14 16:59:19 -07:00
30d452905f Add a --depth option to repo init.
Change-Id: Id30fb4a85f4f8a1847420b0b51a86060041eb5bf
2011-06-09 16:48:23 -07:00
d6c93a28ca Add branch support to repo upload
This commit adds a --br=<branch> option to repo upload.

repo currently examines every non-published branch. This is problematic
for my workflow. I have many branches in my kernel tree. Many of these
branches are based off of upstream remotes (I have many remotes) and
will never be uploaded (they'll get sent upstream as a patch).

Having repo scan these branches adds to my upload processing time
and clutters the branch selection buffer. I've also seen repo get
confused when one of my branches is 1000s of commits different from
m/master.

Change-Id: I68fa18951ea59ba373277b57ffcaf8cddd7e7a40
2011-05-26 10:49:39 -07:00
e7a3bcbbb8 Merge branch 'stable'
* stable:
  Fix mirror clients with no worktree
2011-01-10 13:28:22 -08:00
25b51d8cb7 Merge branch 'stable'
* stable:
  Bump repo version to 1,10
2011-01-10 09:02:23 -08:00
cef005c3e8 Merge branch 'maint'
* maint:
  help: Don't show empty Summary or Description sections
  sync: Run `git gc --auto` after fetch
  Add "repo branch" as an alias for "repo branches"
  upload: Catch and cleanly report connectivity errors
  forall: Silently skip missing projects
  Fix to display the usage message of the command download when the user don't provide any arguments to 'repo download'.
  Use os.environ.copy() instead of dict()
  Make path references OS independent
2011-01-09 17:39:30 -08:00
71cab95b4c Merge branch 'stable'
* stable:
  Encode the environment variables passed to git
  Exit with statuscode 0 for repo help init
2011-01-09 17:29:50 -08:00
9275fd4329 Merge branch 'stable'
* stable:
  Fixed race condition in 'repo sync -jN' that would open multiple masters.
2010-12-22 14:46:15 -08:00
13f3da50d4 Merge branch 'stable'
* stable: (33 commits)
  Added feature to print a <notice> from manifest at the end of a sync.
  sync: Use --force-broken to continue other projects
  upload: Remove --replace option
  sync --quiet: be more quiet
  sync: Enable use of git clone --reference
  Only delete corrupt pickle config files if they exist
  Don't allow git fetch to start ControlMaster
  Check for existing SSH ControlMaster
  Fix for handling values of EDITOR which contain a space.
  upload: Fix --replace flag
  rebase: Pass through more options
  upload: Allow review.HOST.username to override email
  upload -t: Automatically include local branch name
  Warn users before uploading if there are local changes
  sync: Try fetching a tag as a last resort before giving up
  rebase: Automatically rebase branch on upstrea
  upload: Automatically --cc folks in review.URL.autocopy
  Fix format string bugs in grep
  Do not invoke ssh with -p argument when no port has been specified.
  Allow files to be copied into new folders
  ...

Conflicts:
	git_config.py
	manifest_xml.py
	subcmds/init.py
	subcmds/sync.py
	subcmds/upload.py

Change-Id: I4756a6908277e91505c35287a122a775b68f4df5
2010-12-07 11:13:29 -08:00
3218c13205 Use os.environ.copy() instead of dict()
Signed-off-by: Shawn O. Pearce <sop@google.com>
2010-12-07 08:46:14 -08:00
b0f9a02394 Make path references OS independent
Change-Id: I5573995adfd52fd54bddc62d1d1ea78fb1328130
2010-11-29 13:17:53 -06:00
69b1e8aa65 Merge branch 'stable'
* stable:
  Automatically install Gerrit Code Review's commit-msg hook
  Fail sync when encountering "N commits behind."
  Check that we are not overwriting a local repository when syncing.
  Honor url.insteadOf when setting up SSH control master connection
  sync: Fix split call on malformed email addresses
  Fixing project renaming bug.

Conflicts:
	hooks/commit-msg
	project.py
	subcmds/sync.py

Change-Id: I5eaf8fef8cbe4a95d124368112293a9ca64325bf
2010-03-06 19:29:56 -08:00
840ed0fab7 Fix to display the usage message of the command download when the user
don't provide any arguments to 'repo download'.

Signed-off-by: Thiago Farina <thiago.farina@gmail.com>
2009-09-09 00:41:34 -04:00
c024912fb8 commit-msg: Don't create message with only Change-Id
If a user aborts a commit, the commit-msg hook is still called,
but with an empty file.  We need to leave the empty file alone.

Change-Id: I13766135dac267823cb08ab76f67d2000ba2d1ce
Signed-off-by: Shawn O. Pearce <sop@google.com>
2009-08-25 12:03:55 -07:00
15f6579eb3 commit-msg: Update the commit message hook
This version fixes a bug where Change-Id lines become the subject
line, if the subject used a pattern like the subject of this
message does.

Change-Id: I7f7e0363091d03eb05dead2992fc19763214de65
Signed-off-by: Shawn O. Pearce <sop@google.com>
2009-08-22 19:23:55 -07:00
d4cd69bdef forall: Silently skip missing projects
If a project is missing locally, it might be OK to skip over it
and continue running the same command in other projects.

Bug: REPO-43
Change-Id: I64f97eb315f379ab2c51fc53d24ed340b3d09250
Signed-off-by: Shawn O. Pearce <sop@google.com>
2009-08-22 18:51:02 -07:00
d2dfac81ad upload: Catch and cleanly report connectivity errors
Instead of giving a Python backtrace when there is a connectivity
problem during repo upload, report that we cannot access the host,
and why, with a halfway decent error message.

Bug: REPO-45
Change-Id: I9a45b387e86e48073a2d99bd6d594c1a7d6d99d4
Signed-off-by: Shawn O. Pearce <sop@google.com>
2009-08-22 18:41:16 -07:00
4719901067 upload: Document --replace is deprecated
Change-Id: I52715bcfec9c038d0e02505aa7e4054ebc0434aa
Signed-off-by: Shawn O. Pearce <sop@google.com>
2009-08-22 18:41:09 -07:00
a949fa5d20 Automatically install Gerrit Code Review's commit-msg hook
Most users of repo are also using Gerrit Code Review, and will want
the commit-msg hook to be automatically installed into their local
projects so that Change-Ids are assigned when commits are created,
not when they are first uploaded.

Change-Id: Ide42e93b068832f099d68a79c2863d22145d05ad
Signed-off-by: Shawn O. Pearce <sop@google.com>
2009-08-22 18:22:04 -07:00
0afac0856c Merge change 11206
* change 11206:
  Do not invoke ssh with -p argument when no port has been specified.
2009-08-17 08:09:05 -07:00
4c0f670465 Do not invoke ssh with -p argument when no port has been specified.
This change allows local SSH configuration to choose the port number to
use when not explicitly set in the manifest.
2009-08-16 11:26:57 -07:00
33f0e786bb Add "repo branch" as an alias for "repo branches"
For those of us that are used to typing "git branch".

Signed-off-by: Mike Lockwood <lockwood@android.com>
2009-07-14 15:23:39 -04:00
57272ba82e manifest: Support --upgrade to submodule format, from XML
By running `repo manifest --uprade` an administrator can update the
current manifest format from the XML format to submodule format, but
we need all projects to be checked out in a work tree for this to
function correctly.

Signed-off-by: Shawn O. Pearce <sop@google.com>
2009-07-03 20:51:13 -07:00
0125ae2fda Introduce manifest format using git submodules
If a manifest top level directory contains '.gitmodules' we now
assume this is a git module format manifest and switch to using
that code, rather than the legacy XML based manifest.

At the same time, we move the bare repository for a project from
$TOP/.repo/projects/$REPO_PATH.git to be $REPO_NAME.git instead.
This makes it easier for us to later support a repo init from an
existing work tree, as we can more accurately predict the path of
the project's repository in the workspace.  It also means that the
$TOP/.repo/projects/ directory is layed out like a mirror would be.

Signed-off-by: Shawn O. Pearce <sop@google.com>
2009-07-03 20:50:52 -07:00
a7ce096047 Allow meta projects to be created not under .repo/
Some types of manifests might prefer to put their meta project work
tree under topdir, rather than inside of the .repo/ directory.  We
can support that by allowing relpath to be optionally passed in.

Signed-off-by: Shawn O. Pearce <sop@google.com>
2009-07-03 20:50:52 -07:00
87bda12e85 sync: Support upgrading manifest formats
If the manifest format changes during init or sync we need to do
a full reparse of the manifest, and possibly allow the new object
to reconfigure the local workspace to match its expectations.

Signed-off-by: Shawn O. Pearce <sop@google.com>
2009-07-03 20:50:52 -07:00
5f947bba69 init: add -o, --origin to name manifest remote
The -o option permits the user to control the name of the manifest's
remote, which normally is hardcoded to be 'origin', but can differ
because we derive it at runtime from the configuration file.

Signed-off-by: Shawn O. Pearce <sop@google.com>
2009-07-03 20:50:52 -07:00
b3d2c9214b init (wrapper): Note that -m is now deprecated
If the manifest format isn't XML, this option isn't available.

Signed-off-by: Shawn O. Pearce <sop@google.com>
2009-07-03 20:50:52 -07:00
7354d88914 init: Ensure repo.mirror is noticed once set
If we don't clear the cache, there can be a timestamp race between
the pickle file and the raw text file, and we may not pick up the
edit when we create a new config object around the same path name.

Signed-off-by: Shawn O. Pearce <sop@google.com>
2009-07-03 20:50:52 -07:00
ce86abbe8a Allow the manifest to be accessed it if is in work tree
If the manifest's work tree is actually inside of the rest of
the client work tree then its only fair that we include it as
a project that the user can access.

Signed-off-by: Shawn O. Pearce <sop@google.com>
2009-07-03 20:03:38 -07:00
75b87c8a51 Abstract manifest branch creation from init to the manifest object
This permits the XML style manifest to use 'default', while other
types can use their own creation strategy for the current branch.

Signed-off-by: Shawn O. Pearce <sop@google.com>
2009-07-03 20:03:38 -07:00
abb7a3dfec Allow callers to request a specific type of manifest
If the caller knows exactly what the manifest type must be we
can now ask the loader to directly construct that type, rather
than guessing it from the working directory.

Signed-off-by: Shawn O. Pearce <sop@google.com>
2009-07-03 20:03:38 -07:00
cc6c79643e Make refs/remotes/m management the manifest object's responsibility
I plan to have the new submodule manifest format use a different
layout for the m refs than the XML manifest format has used in
the past.  Thus we need to move the behavior management into the
manifest object, and out of the project, so we can change it.

Signed-off-by: Shawn O. Pearce <sop@google.com>
2009-07-03 20:03:38 -07:00
2095179bee Cleanup import formatting in manifest_xml.py
Signed-off-by: Shawn O. Pearce <sop@google.com>
2009-07-03 20:03:38 -07:00
b0ca41e19a Only remove a stale pickle file if it exists
Signed-off-by: Shawn O. Pearce <sop@google.com>
2009-07-03 20:03:38 -07:00
1875ddd47c sync: Run git gc --auto after fetch
Users may wind up with a lot of loose object content in projects they
don't frequently make changes in, but that are modified by others.

Since we bypass many git code paths that would have otherwise called
out to `git gc --auto`, its possible for these projects to have
their loose object database grow out of control.  To help prevent
that, we now invoke it ourselves during the network half of sync.

Signed-off-by: Shawn O. Pearce <sop@google.com>
2009-07-03 16:39:19 -07:00
446c4e5556 init: Allow -m only on XML formatted manifest
If the manifest is the newer SubmoduleManifest style, then the -m
option makes no sense, as you cannot select a specific file within
the current branch.

Signed-off-by: Shawn O. Pearce <sop@google.com>
2009-07-03 11:00:16 -07:00
67f4563acb manifest: Only support -o option on XML formatted manifest
If the manifest isn't a single file format manifest, the -o option
makes no sense, as you cannot export multiple files to a single
stream for display or redirection.

Signed-off-by: Shawn O. Pearce <sop@google.com>
2009-07-03 11:00:16 -07:00
050e4fd591 manifest: Only display XML help on XML manifest
Some of the help text is only related to the XML formatted manifest,
so only display that text if that is the current format.

Signed-off-by: Shawn O. Pearce <sop@google.com>
2009-07-03 11:00:16 -07:00
60e679209a help: Don't show empty Summary or Description sections
Signed-off-by: Shawn O. Pearce <sop@google.com>
2009-07-03 11:00:16 -07:00
f1a6b14fdc Create an abstract Manifest base class
This will help as we add support for another manifest type.

Signed-off-by: Shawn O. Pearce <sop@google.com>
2009-07-03 11:00:16 -07:00
ca3d8ff4fc Teach Project how to relink a .git/ in the work tree
The _LinkWorkTree method can now be used to relink the work tree,
such as if the real repository was moved to a different location
on disk.

Signed-off-by: Shawn O. Pearce <sop@google.com>
2009-07-03 11:00:16 -07:00
98ea26b8d8 Allow callers to reset the git config cache
If commands modify the git config too rapidly we might not notice
the .git/config file has been modified, as they could run in the
same filesystem timestamp window and thus not cause a change on
the config's mtime.  This can cause repo to miss re-reading the
config file after running a command.

Allowing the cache to be flushed forces us to re-read the config.

Signed-off-by: Shawn O. Pearce <sop@google.com>
2009-07-03 11:00:16 -07:00
52 changed files with 5603 additions and 1645 deletions

4
.gitattributes vendored Normal file
View File

@ -0,0 +1,4 @@
# Prevent /bin/sh scripts from being clobbered by autocrlf=true
git_ssh text eol=lf
main.py text eol=lf
repo text eol=lf

1
.gitignore vendored
View File

@ -1,2 +1,3 @@
*.pyc
.repopickle_*
/repoc

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>repo</name>
<name>git-repo</name>
<comment></comment>
<projects>
</projects>

View File

@ -3,8 +3,8 @@
<pydev_project>
<pydev_pathproperty name="org.python.pydev.PROJECT_SOURCE_PATH">
<path>/repo</path>
<path>/git-repo</path>
</pydev_pathproperty>
<pydev_property name="org.python.pydev.PYTHON_PROJECT_VERSION">python 2.4</pydev_property>
<pydev_property name="org.python.pydev.PYTHON_PROJECT_VERSION">python 2.6</pydev_property>
<pydev_property name="org.python.pydev.PYTHON_PROJECT_INTERPRETER">Default</pydev_property>
</pydev_project>

298
.pylintrc Normal file
View File

@ -0,0 +1,298 @@
# lint Python modules using external checkers.
#
# This is the main checker controling the other ones and the reports
# generation. It is itself both a raw checker and an astng checker in order
# to:
# * handle message activation / deactivation at the module level
# * handle some basic but necessary stats'data (number of classes, methods...)
#
[MASTER]
# Specify a configuration file.
#rcfile=
# Python code to execute, usually for sys.path manipulation such as
# pygtk.require().
#init-hook=
# Profiled execution.
profile=no
# Add <file or directory> to the black list. It should be a base name, not a
# path. You may set this option multiple times.
ignore=SVN
# Pickle collected data for later comparisons.
persistent=yes
# Set the cache size for astng objects.
cache-size=500
# List of plugins (as comma separated values of python modules names) to load,
# usually to register additional checkers.
load-plugins=
[MESSAGES CONTROL]
# Enable only checker(s) with the given id(s). This option conflicts with the
# disable-checker option
#enable-checker=
# Enable all checker(s) except those with the given id(s). This option
# conflicts with the enable-checker option
#disable-checker=
# Enable all messages in the listed categories.
#enable-msg-cat=
# Disable all messages in the listed categories.
#disable-msg-cat=
# Enable the message(s) with the given id(s).
enable=RP0004
# Disable the message(s) with the given id(s).
disable=R0903,R0912,R0913,R0914,R0915,W0141,C0111,C0103,W0603,W0703,R0911,C0301,C0302,R0902,R0904,W0142,W0212,E1101,E1103,R0201,W0201,W0122,W0232,RP0001,RP0003,RP0101,RP0002,RP0401,RP0701,RP0801,F0401,E0611,R0801,I0011
[REPORTS]
# set the output format. Available formats are text, parseable, colorized, msvs
# (visual studio) and html
output-format=text
# Put messages in a separate file for each module / package specified on the
# command line instead of printing them on stdout. Reports (if any) will be
# written in a file name "pylint_global.[txt|html]".
files-output=no
# Tells whether to display a full report or only the messages
reports=yes
# Python expression which should return a note less than 10 (10 is the highest
# note).You have access to the variables errors warning, statement which
# respectivly contain the number of errors / warnings messages and the total
# number of statements analyzed. This is used by the global evaluation report
# (R0004).
evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
# Add a comment according to your evaluation note. This is used by the global
# evaluation report (R0004).
comment=no
# checks for
# * unused variables / imports
# * undefined variables
# * redefinition of variable from builtins or from an outer scope
# * use of variable before assigment
#
[VARIABLES]
# Tells whether we should check for unused import in __init__ files.
init-import=no
# A regular expression matching names used for dummy variables (i.e. not used).
dummy-variables-rgx=_|dummy
# List of additional names supposed to be defined in builtins. Remember that
# you should avoid to define new builtins when possible.
additional-builtins=
# try to find bugs in the code using type inference
#
[TYPECHECK]
# Tells whether missing members accessed in mixin class should be ignored. A
# mixin class is detected if its name ends with "mixin" (case insensitive).
ignore-mixin-members=yes
# List of classes names for which member attributes should not be checked
# (useful for classes with attributes dynamicaly set).
ignored-classes=SQLObject
# When zope mode is activated, consider the acquired-members option to ignore
# access to some undefined attributes.
zope=no
# List of members which are usually get through zope's acquisition mecanism and
# so shouldn't trigger E0201 when accessed (need zope=yes to be considered).
acquired-members=REQUEST,acl_users,aq_parent
# checks for :
# * doc strings
# * modules / classes / functions / methods / arguments / variables name
# * number of arguments, local variables, branchs, returns and statements in
# functions, methods
# * required module attributes
# * dangerous default values as arguments
# * redefinition of function / method / class
# * uses of the global statement
#
[BASIC]
# Required attributes for module, separated by a comma
required-attributes=
# Regular expression which should only match functions or classes name which do
# not require a docstring
no-docstring-rgx=_main|__.*__
# Regular expression which should only match correct module names
module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
# Regular expression which should only match correct module level names
const-rgx=(([A-Z_][A-Z1-9_]*)|(__.*__))|(log)$
# Regular expression which should only match correct class names
class-rgx=[A-Z_][a-zA-Z0-9]+$
# Regular expression which should only match correct function names
function-rgx=[a-z_][a-z0-9_]{2,30}$
# Regular expression which should only match correct method names
method-rgx=[a-z_][a-z0-9_]{2,30}$
# Regular expression which should only match correct instance attribute names
attr-rgx=[a-z_][a-z0-9_]{2,30}$
# Regular expression which should only match correct argument names
argument-rgx=[a-z_][a-z0-9_]{2,30}$
# Regular expression which should only match correct variable names
variable-rgx=[a-z_][a-z0-9_]{2,30}$
# Regular expression which should only match correct list comprehension /
# generator expression variable names
inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$
# Good variable names which should always be accepted, separated by a comma
good-names=i,j,k,ex,Run,_,e,d1,d2,v,f,l,d
# Bad variable names which should always be refused, separated by a comma
bad-names=foo,bar,baz,toto,tutu,tata
# List of builtins function names that should not be used, separated by a comma
bad-functions=map,filter,apply,input
# checks for sign of poor/misdesign:
# * number of methods, attributes, local variables...
# * size, complexity of functions, methods
#
[DESIGN]
# Maximum number of arguments for function / method
max-args=5
# Maximum number of locals for function / method body
max-locals=15
# Maximum number of return / yield for function / method body
max-returns=6
# Maximum number of branch for function / method body
max-branchs=12
# Maximum number of statements in function / method body
max-statements=50
# Maximum number of parents for a class (see R0901).
max-parents=7
# Maximum number of attributes for a class (see R0902).
max-attributes=20
# Minimum number of public methods for a class (see R0903).
min-public-methods=2
# Maximum number of public methods for a class (see R0904).
max-public-methods=30
# checks for
# * external modules dependencies
# * relative / wildcard imports
# * cyclic imports
# * uses of deprecated modules
#
[IMPORTS]
# Deprecated modules which should not be used, separated by a comma
deprecated-modules=regsub,string,TERMIOS,Bastion,rexec
# Create a graph of every (i.e. internal and external) dependencies in the
# given file (report R0402 must not be disabled)
import-graph=
# Create a graph of external dependencies in the given file (report R0402 must
# not be disabled)
ext-import-graph=
# Create a graph of internal dependencies in the given file (report R0402 must
# not be disabled)
int-import-graph=
# checks for :
# * methods without self as first argument
# * overridden methods signature
# * access only to existant members via self
# * attributes not defined in the __init__ method
# * supported interfaces implementation
# * unreachable code
#
[CLASSES]
# List of interface methods to ignore, separated by a comma. This is used for
# instance to not check methods defines in Zope's Interface base class.
ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by
# List of method names used to declare (i.e. assign) instance attributes.
defining-attr-methods=__init__,__new__,setUp
# checks for similarities and duplicated code. This computation may be
# memory / CPU intensive, so you should disable it if you experiments some
# problems.
#
[SIMILARITIES]
# Minimum lines number of a similarity.
min-similarity-lines=4
# Ignore comments when computing similarities.
ignore-comments=yes
# Ignore docstrings when computing similarities.
ignore-docstrings=yes
# checks for:
# * warning notes in the code like FIXME, XXX
# * PEP 263: source code with non ascii character but no encoding declaration
#
[MISCELLANEOUS]
# List of note tags to take in consideration, separated by a comma.
notes=FIXME,XXX,TODO
# checks for :
# * unauthorized constructions
# * strict indentation
# * line length
# * use of <> instead of !=
#
[FORMAT]
# Maximum number of characters on a single line.
max-line-length=80
# Maximum number of lines in a module
max-module-lines=1000
# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
# tab). In repo it is 2 spaces.
indent-string=' '

View File

@ -2,10 +2,11 @@ Short Version:
- Make small logical changes.
- Provide a meaningful commit message.
- Check for coding errors with pylint
- Make sure all code is under the Apache License, 2.0.
- Publish your changes for review:
git push ssh://review.source.android.com:29418/tools/repo.git HEAD:refs/for/master
git push https://gerrit-review.googlesource.com/git-repo HEAD:refs/for/master
Long Version:
@ -33,7 +34,14 @@ If your description starts to get too long, that's a sign that you
probably need to split up your commit to finer grained pieces.
(2) Check the license
(2) Check for coding errors with pylint
Run pylint on changed modules using the provided configuration:
pylint --rcfile=.pylintrc file.py
(3) Check the license
repo is licensed under the Apache License, 2.0.
@ -49,29 +57,28 @@ your patch. It is virtually impossible to remove a patch once it
has been applied and pushed out.
(3) Sending your patches.
(4) Sending your patches.
Do not email your patches to anyone.
Instead, login to the Gerrit Code Review tool at:
https://review.source.android.com/
https://gerrit-review.googlesource.com/
Ensure you have completed one of the necessary contributor
agreements, providing documentation to the project maintainers that
they have right to redistribute your work under the Apache License:
https://review.source.android.com/#settings,agreements
https://gerrit-review.googlesource.com/#/settings/agreements
Ensure you have registered one or more SSH public keys, so you can
push your commits directly over SSH:
Ensure you have obtained an HTTP password to authenticate:
https://review.source.android.com/#settings,ssh-keys
https://gerrit-review.googlesource.com/new-password
Push your patches over SSH to the review server, possibly through
Push your patches over HTTPS to the review server, possibly through
a remembered remote to make this easier in the future:
git config remote.review.url ssh://review.source.android.com:29418/tools/repo.git
git config remote.review.url https://gerrit-review.googlesource.com/git-repo
git config remote.review.push HEAD:refs/for/master
git push review

162
color.py
View File

@ -17,78 +17,105 @@ import os
import sys
import pager
from git_config import GitConfig
COLORS = {None :-1,
'normal' :-1,
'black' : 0,
'red' : 1,
'green' : 2,
'yellow' : 3,
'blue' : 4,
COLORS = {None: -1,
'normal': -1,
'black': 0,
'red': 1,
'green': 2,
'yellow': 3,
'blue': 4,
'magenta': 5,
'cyan' : 6,
'white' : 7}
'cyan': 6,
'white': 7}
ATTRS = {None :-1,
'bold' : 1,
'dim' : 2,
'ul' : 4,
'blink' : 5,
ATTRS = {None: -1,
'bold': 1,
'dim': 2,
'ul': 4,
'blink': 5,
'reverse': 7}
RESET = "\033[m"
def is_color(s): return s in COLORS
def is_attr(s): return s in ATTRS
def _Color(fg = None, bg = None, attr = None):
fg = COLORS[fg]
bg = COLORS[bg]
attr = ATTRS[attr]
def is_color(s):
return s in COLORS
if attr >= 0 or fg >= 0 or bg >= 0:
need_sep = False
code = "\033["
if attr >= 0:
code += chr(ord('0') + attr)
need_sep = True
def is_attr(s):
return s in ATTRS
if fg >= 0:
if need_sep:
code += ';'
need_sep = True
if fg < 8:
code += '3%c' % (ord('0') + fg)
else:
code += '38;5;%d' % fg
def _Color(fg=None, bg=None, attr=None):
fg = COLORS[fg]
bg = COLORS[bg]
attr = ATTRS[attr]
if bg >= 0:
if need_sep:
code += ';'
need_sep = True
if attr >= 0 or fg >= 0 or bg >= 0:
need_sep = False
code = "\033["
if bg < 8:
code += '4%c' % (ord('0') + bg)
else:
code += '48;5;%d' % bg
code += 'm'
else:
code = ''
return code
if attr >= 0:
code += chr(ord('0') + attr)
need_sep = True
if fg >= 0:
if need_sep:
code += ';'
need_sep = True
if fg < 8:
code += '3%c' % (ord('0') + fg)
else:
code += '38;5;%d' % fg
if bg >= 0:
if need_sep:
code += ';'
if bg < 8:
code += '4%c' % (ord('0') + bg)
else:
code += '48;5;%d' % bg
code += 'm'
else:
code = ''
return code
DEFAULT = None
def SetDefaultColoring(state):
"""Set coloring behavior to |state|.
This is useful for overriding config options via the command line.
"""
if state is None:
# Leave it alone -- return quick!
return
global DEFAULT
state = state.lower()
if state in ('auto',):
DEFAULT = state
elif state in ('always', 'yes', 'true', True):
DEFAULT = 'always'
elif state in ('never', 'no', 'false', False):
DEFAULT = 'never'
class Coloring(object):
def __init__(self, config, type):
self._section = 'color.%s' % type
def __init__(self, config, section_type):
self._section = 'color.%s' % section_type
self._config = config
self._out = sys.stdout
on = self._config.GetString(self._section)
on = DEFAULT
if on is None:
on = self._config.GetString('color.ui')
on = self._config.GetString(self._section)
if on is None:
on = self._config.GetString('color.ui')
if on == 'auto':
if pager.active or os.isatty(1):
@ -119,22 +146,45 @@ class Coloring(object):
def printer(self, opt=None, fg=None, bg=None, attr=None):
s = self
c = self.colorer(opt, fg, bg, attr)
def f(fmt, *args):
s._out.write(c(fmt, *args))
return f
def nofmt_printer(self, opt=None, fg=None, bg=None, attr=None):
s = self
c = self.nofmt_colorer(opt, fg, bg, attr)
def f(fmt):
s._out.write(c(fmt))
return f
def colorer(self, opt=None, fg=None, bg=None, attr=None):
if self._on:
c = self._parse(opt, fg, bg, attr)
def f(fmt, *args):
str = fmt % args
return ''.join([c, str, RESET])
output = fmt % args
return ''.join([c, output, RESET])
return f
else:
def f(fmt, *args):
return fmt % args
return f
def nofmt_colorer(self, opt=None, fg=None, bg=None, attr=None):
if self._on:
c = self._parse(opt, fg, bg, attr)
def f(fmt):
return ''.join([c, fmt, RESET])
return f
else:
def f(fmt):
return fmt
return f
def _parse(self, opt, fg, bg, attr):
if not opt:
return _Color(fg, bg, attr)
@ -152,8 +202,10 @@ class Coloring(object):
have_fg = False
for a in v.split(' '):
if is_color(a):
if have_fg: bg = a
else: fg = a
if have_fg:
bg = a
else:
fg = a
elif is_attr(a):
attr = a

View File

@ -15,9 +15,13 @@
import os
import optparse
import platform
import re
import sys
from error import NoSuchProjectError
from error import InvalidProjectGroupsError
class Command(object):
"""Base class for any command line action in repo.
@ -30,6 +34,27 @@ class Command(object):
def WantPager(self, opt):
return False
def ReadEnvironmentOptions(self, opts):
""" Set options from environment variables. """
env_options = self._RegisteredEnvironmentOptions()
for env_key, opt_key in env_options.items():
# Get the user-set option value if any
opt_value = getattr(opts, opt_key)
# If the value is set, it means the user has passed it as a command
# line option, and we should use that. Otherwise we can try to set it
# with the value from the corresponding environment variable.
if opt_value is not None:
continue
env_value = os.environ.get(env_key)
if env_value is not None:
setattr(opts, opt_key, env_value)
return opts
@property
def OptionParser(self):
if self._optparse is None:
@ -46,6 +71,24 @@ class Command(object):
"""Initialize the option parser.
"""
def _RegisteredEnvironmentOptions(self):
"""Get options that can be set from environment variables.
Return a dictionary mapping environment variable name
to option key name that it can override.
Example: {'REPO_MY_OPTION': 'my_option'}
Will allow the option with key value 'my_option' to be set
from the value in the environment variable named 'REPO_MY_OPTION'.
Note: This does not work properly for options that are explicitly
set to None by the user, or options that are defined with a
default value other than None.
"""
return {}
def Usage(self):
"""Display usage and terminate.
"""
@ -56,60 +99,113 @@ class Command(object):
"""Perform the action, after option parsing is complete.
"""
raise NotImplementedError
def GetProjects(self, args, missing_ok=False):
def _ResetPathToProjectMap(self, projects):
self._by_path = dict((p.worktree, p) for p in projects)
def _UpdatePathToProjectMap(self, project):
self._by_path[project.worktree] = project
def _GetProjectByPath(self, path):
project = None
if os.path.exists(path):
oldpath = None
while path \
and path != oldpath \
and path != self.manifest.topdir:
try:
project = self._by_path[path]
break
except KeyError:
oldpath = path
path = os.path.dirname(path)
else:
try:
project = self._by_path[path]
except KeyError:
pass
return project
def GetProjects(self, args, missing_ok=False, submodules_ok=False):
"""A list of projects that match the arguments.
"""
all = self.manifest.projects
all_projects_list = self.manifest.projects
result = []
mp = self.manifest.manifestProject
groups = mp.config.GetString('manifest.groups')
if not groups:
groups = 'default,platform-' + platform.system().lower()
groups = [x for x in re.split(r'[,\s]+', groups) if x]
if not args:
for project in all.values():
if missing_ok or project.Exists:
derived_projects = {}
for project in all_projects_list:
if submodules_ok or project.sync_s:
derived_projects.update((p.name, p)
for p in project.GetDerivedSubprojects())
all_projects_list.extend(derived_projects.values())
for project in all_projects_list:
if ((missing_ok or project.Exists) and
project.MatchesGroups(groups)):
result.append(project)
else:
by_path = None
self._ResetPathToProjectMap(all_projects_list)
for arg in args:
project = all.get(arg)
projects = self.manifest.GetProjectsWithName(arg)
if not project:
if not projects:
path = os.path.abspath(arg).replace('\\', '/')
project = self._GetProjectByPath(path)
if not by_path:
by_path = dict()
for p in all.values():
by_path[p.worktree] = p
# If it's not a derived project, update path->project mapping and
# search again, as arg might actually point to a derived subproject.
if (project and not project.Derived and
(submodules_ok or project.sync_s)):
search_again = False
for subproject in project.GetDerivedSubprojects():
self._UpdatePathToProjectMap(subproject)
search_again = True
if search_again:
project = self._GetProjectByPath(path) or project
if os.path.exists(path):
oldpath = None
while path \
and path != oldpath \
and path != self.manifest.topdir:
try:
project = by_path[path]
break
except KeyError:
oldpath = path
path = os.path.dirname(path)
else:
try:
project = by_path[path]
except KeyError:
pass
if project:
projects = [project]
if not project:
raise NoSuchProjectError(arg)
if not missing_ok and not project.Exists:
if not projects:
raise NoSuchProjectError(arg)
result.append(project)
for project in projects:
if not missing_ok and not project.Exists:
raise NoSuchProjectError(arg)
if not project.MatchesGroups(groups):
raise InvalidProjectGroupsError(arg)
result.extend(projects)
def _getpath(x):
return x.relpath
result.sort(key=_getpath)
return result
def FindProjects(self, args):
result = []
patterns = [re.compile(r'%s' % a, re.IGNORECASE) for a in args]
for project in self.GetProjects(''):
for pattern in patterns:
if pattern.search(project.name) or pattern.search(project.relpath):
result.append(project)
break
result.sort(key=lambda project: project.relpath)
return result
# pylint: disable=W0223
# Pylint warns that the `InteractiveCommand` and `PagedCommand` classes do not
# override method `Execute` which is abstract in `Command`. Since that method
# is always implemented in classes derived from `InteractiveCommand` and
# `PagedCommand`, this warning can be suppressed.
class InteractiveCommand(Command):
"""Command which requires user interaction on the tty and
must not run within a pager, even if the user asks to.
@ -124,6 +220,8 @@ class PagedCommand(Command):
def WantPager(self, opt):
return True
# pylint: enable=W0223
class MirrorSafeCommand(object):
"""Command permits itself to run within a mirror,
and does not require a working directory.

View File

@ -26,34 +26,62 @@ following DTD:
manifest-server?,
remove-project*,
project*,
extend-project*,
repo-hooks?)>
<!ELEMENT notice (#PCDATA)>
<!ELEMENT remote (EMPTY)>
<!ATTLIST remote name ID #REQUIRED>
<!ATTLIST remote alias CDATA #IMPLIED>
<!ATTLIST remote fetch CDATA #REQUIRED>
<!ATTLIST remote review CDATA #IMPLIED>
<!ATTLIST remote revision CDATA #IMPLIED>
<!ELEMENT default (EMPTY)>
<!ATTLIST default remote IDREF #IMPLIED>
<!ATTLIST default revision CDATA #IMPLIED>
<!ATTLIST default remote IDREF #IMPLIED>
<!ATTLIST default revision CDATA #IMPLIED>
<!ATTLIST default dest-branch CDATA #IMPLIED>
<!ATTLIST default sync-j CDATA #IMPLIED>
<!ATTLIST default sync-c CDATA #IMPLIED>
<!ATTLIST default sync-s CDATA #IMPLIED>
<!ELEMENT manifest-server (EMPTY)>
<!ATTLIST url CDATA #REQUIRED>
<!ELEMENT project (EMPTY)>
<!ATTLIST project name CDATA #REQUIRED>
<!ATTLIST project path CDATA #IMPLIED>
<!ATTLIST project remote IDREF #IMPLIED>
<!ATTLIST project revision CDATA #IMPLIED>
<!ELEMENT project (annotation*,
project*)>
<!ATTLIST project name CDATA #REQUIRED>
<!ATTLIST project path CDATA #IMPLIED>
<!ATTLIST project remote IDREF #IMPLIED>
<!ATTLIST project revision CDATA #IMPLIED>
<!ATTLIST project dest-branch CDATA #IMPLIED>
<!ATTLIST project groups CDATA #IMPLIED>
<!ATTLIST project sync-c CDATA #IMPLIED>
<!ATTLIST project sync-s CDATA #IMPLIED>
<!ATTLIST project upstream CDATA #IMPLIED>
<!ATTLIST project clone-depth CDATA #IMPLIED>
<!ATTLIST project force-path CDATA #IMPLIED>
<!ELEMENT annotation (EMPTY)>
<!ATTLIST annotation name CDATA #REQUIRED>
<!ATTLIST annotation value CDATA #REQUIRED>
<!ATTLIST annotation keep CDATA "true">
<!ELEMENT extend-project>
<!ATTLIST extend-project name CDATA #REQUIRED>
<!ATTLIST extend-project path CDATA #IMPLIED>
<!ATTLIST extend-project groups CDATA #IMPLIED>
<!ELEMENT remove-project (EMPTY)>
<!ATTLIST remove-project name CDATA #REQUIRED>
<!ELEMENT repo-hooks (EMPTY)>
<!ATTLIST repo-hooks in-project CDATA #REQUIRED>
<!ATTLIST repo-hooks enabled-list CDATA #REQUIRED>
<!ELEMENT include (EMPTY)>
<!ATTLIST include name CDATA #REQUIRED>
]>
A description of the elements and their attributes follows.
@ -77,6 +105,12 @@ name specified here is used as the remote name in each project's
.git/config, and is therefore automatically available to commands
like `git fetch`, `git remote`, `git pull` and `git push`.
Attribute `alias`: The alias, if specified, is used to override
`name` to be set as the remote name in each project's .git/config.
Its value can be duplicated while attribute `name` has to be unique
in the manifest file. This helps each project to be able to have
same remote name which actually points to different remote url.
Attribute `fetch`: The Git URL prefix for all projects which use
this remote. Each project's name is appended to this prefix to
form the actual URL used to clone the project.
@ -85,6 +119,10 @@ Attribute `review`: Hostname of the Gerrit server where reviews
are uploaded to by `repo upload`. This attribute is optional;
if not specified then `repo upload` will not function.
Attribute `revision`: Name of a Git branch (e.g. `master` or
`refs/heads/master`). Remotes with their own revision will override
the default revision.
Element default
---------------
@ -100,34 +138,57 @@ Attribute `revision`: Name of a Git branch (e.g. `master` or
`refs/heads/master`). Project elements lacking their own
revision attribute will use this revision.
Attribute `dest-branch`: Name of a Git branch (e.g. `master`).
Project elements not setting their own `dest-branch` will inherit
this value. If this value is not set, projects will use `revision`
by default instead.
Attribute `sync-j`: Number of parallel jobs to use when synching.
Attribute `sync-c`: Set to true to only sync the given Git
branch (specified in the `revision` attribute) rather than the
whole ref space. Project elements lacking a sync-c element of
their own will use this value.
Attribute `sync-s`: Set to true to also sync sub-projects.
Element manifest-server
-----------------------
At most one manifest-server may be specified. The url attribute
is used to specify the URL of a manifest server, which is an
XML RPC service that will return a manifest in which each project
is pegged to a known good revision for the current branch and
target.
XML RPC service.
The manifest server should implement:
The manifest server should implement the following RPC methods:
GetApprovedManifest(branch, target)
Return a manifest in which each project is pegged to a known good revision
for the current branch and target.
The target to use is defined by environment variables TARGET_PRODUCT
and TARGET_BUILD_VARIANT. These variables are used to create a string
of the form $TARGET_PRODUCT-$TARGET_BUILD_VARIANT, e.g. passion-userdebug.
If one of those variables or both are not present, the program will call
GetApprovedManifest without the target paramater and the manifest server
GetApprovedManifest without the target parameter and the manifest server
should choose a reasonable default target.
GetManifest(tag)
Return a manifest in which each project is pegged to the revision at
the specified tag.
Element project
---------------
One or more project elements may be specified. Each element
describes a single Git repository to be cloned into the repo
client workspace.
client workspace. You may specify Git-submodules by creating a
nested project. Git-submodules will be automatically
recognized and inherit their parent's attributes, but those
may be overridden by an explicitly specified project element.
Attribute `name`: A unique name for this project. The project's
name is appended onto its remote's fetch URL to generate the actual
@ -137,8 +198,9 @@ URL to configure the Git remote with. The URL gets formed as:
where ${remote_fetch} is the remote's fetch attribute and
${project_name} is the project's name attribute. The suffix ".git"
is always appended as repo assumes the upstream is a forrest of
bare Git repositories.
is always appended as repo assumes the upstream is a forest of
bare Git repositories. If the project has a parent element, its
name will be prefixed by the parent's.
The project name must match the name Gerrit knows, if Gerrit is
being used for code reviews.
@ -146,6 +208,8 @@ being used for code reviews.
Attribute `path`: An optional path relative to the top directory
of the repo client where the Git working directory for this project
should be placed. If not supplied the project name is used.
If the project has a parent element, its path will be prefixed
by the parent's.
Attribute `remote`: Name of a previously defined remote element.
If not supplied the remote given by the default element is used.
@ -155,7 +219,71 @@ to track for this project. Names can be relative to refs/heads
(e.g. just "master") or absolute (e.g. "refs/heads/master").
Tags and/or explicit SHA-1s should work in theory, but have not
been extensively tested. If not supplied the revision given by
the default element is used.
the remote element is used if applicable, else the default
element is used.
Attribute `dest-branch`: Name of a Git branch (e.g. `master`).
When using `repo upload`, changes will be submitted for code
review on this branch. If unspecified both here and in the
default element, `revision` is used instead.
Attribute `groups`: List of groups to which this project belongs,
whitespace or comma separated. All projects belong to the group
"all", and each project automatically belongs to a group of
its name:`name` and path:`path`. E.g. for
<project name="monkeys" path="barrel-of"/>, that project
definition is implicitly in the following manifest groups:
default, name:monkeys, and path:barrel-of. If you place a project in the
group "notdefault", it will not be automatically downloaded by repo.
If the project has a parent element, the `name` and `path` here
are the prefixed ones.
Attribute `sync-c`: Set to true to only sync the given Git
branch (specified in the `revision` attribute) rather than the
whole ref space.
Attribute `sync-s`: Set to true to also sync sub-projects.
Attribute `upstream`: Name of the Git ref in which a sha1
can be found. Used when syncing a revision locked manifest in
-c mode to avoid having to sync the entire ref space.
Attribute `clone-depth`: Set the depth to use when fetching this
project. If specified, this value will override any value given
to repo init with the --depth option on the command line.
Attribute `force-path`: Set to true to force this project to create the
local mirror repository according to its `path` attribute (if supplied)
rather than the `name` attribute. This attribute only applies to the
local mirrors syncing, it will be ignored when syncing the projects in a
client working directory.
Element extend-project
----------------------
Modify the attributes of the named project.
This element is mostly useful in a local manifest file, to modify the
attributes of an existing project without completely replacing the
existing project definition. This makes the local manifest more robust
against changes to the original manifest.
Attribute `path`: If specified, limit the change to projects checked out
at the specified path, rather than all projects with the given name.
Attribute `groups`: List of additional groups to which this project
belongs. Same syntax as the corresponding element of `project`.
Element annotation
------------------
Zero or more annotation elements may be specified as children of a
project element. Each element describes a name-value pair that will be
exported into each project's environment during a 'forall' command,
prefixed with REPO__. In addition, there is an optional attribute
"keep" which accepts the case insensitive values "true" (default) or
"false". This attribute determines whether or not the annotation will
be kept when exported with the manifest subcommand.
Element remove-project
----------------------
@ -164,20 +292,34 @@ Deletes the named project from the internal manifest table, possibly
allowing a subsequent project element in the same manifest file to
replace the project with a different source.
This element is mostly useful in the local_manifest.xml, where
This element is mostly useful in a local manifest file, where
the user can remove a project, and possibly replace it with their
own definition.
Element include
---------------
Local Manifest
==============
This element provides the capability of including another manifest
file into the originating manifest. Normal rules apply for the
target manifest to include - it must be a usable manifest on its own.
Additional remotes and projects may be added through a local
manifest, stored in `$TOP_DIR/.repo/local_manifest.xml`.
Attribute `name`: the manifest to include, specified relative to
the manifest repository's root.
Local Manifests
===============
Additional remotes and projects may be added through local manifest
files stored in `$TOP_DIR/.repo/local_manifests/*.xml`.
For example:
$ cat .repo/local_manifest.xml
$ ls .repo/local_manifests
local_manifest.xml
another_local_manifest.xml
$ cat .repo/local_manifests/local_manifest.xml
<?xml version="1.0" encoding="UTF-8"?>
<manifest>
<project path="manifest"
@ -186,6 +328,17 @@ For example:
name="platform/manifest" />
</manifest>
Users may add projects to the local manifest prior to a `repo sync`
Users may add projects to the local manifest(s) prior to a `repo sync`
invocation, instructing repo to automatically download and manage
these extra projects.
Manifest files stored in `$TOP_DIR/.repo/local_manifests/*.xml` will
be loaded in alphabetical order.
Additional remotes and projects may also be added through a local
manifest, stored in `$TOP_DIR/.repo/local_manifest.xml`. This method
is deprecated in favor of using multiple manifest files as mentioned
above.
If `$TOP_DIR/.repo/local_manifest.xml` exists, it will be loaded before
any manifest files stored in `$TOP_DIR/.repo/local_manifests/*.xml`.

View File

@ -13,6 +13,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import print_function
import os
import re
import sys
@ -53,10 +54,10 @@ class Editor(object):
return e
if os.getenv('TERM') == 'dumb':
print >>sys.stderr,\
print(
"""No editor specified in GIT_EDITOR, core.editor, VISUAL or EDITOR.
Tried to fall back to vi but terminal is dumb. Please configure at
least one of these before using this command."""
least one of these before using this command.""", file=sys.stderr)
sys.exit(1)
return 'vi'
@ -67,7 +68,7 @@ least one of these before using this command."""
Args:
data : the text to edit
Returns:
new value of edited text; None if editing did not succeed
"""
@ -91,7 +92,7 @@ least one of these before using this command."""
try:
rc = subprocess.Popen(args, shell=shell).wait()
except OSError, e:
except OSError as e:
raise EditorError('editor failed, %s: %s %s'
% (str(e), editor, path))
if rc != 0:

View File

@ -21,10 +21,22 @@ class ManifestInvalidRevisionError(Exception):
"""The revision value in a project is incorrect.
"""
class NoManifestException(Exception):
"""The required manifest does not exist.
"""
def __init__(self, path, reason):
super(NoManifestException, self).__init__()
self.path = path
self.reason = reason
def __str__(self):
return self.reason
class EditorError(Exception):
"""Unspecified error from the user's text editor.
"""
def __init__(self, reason):
super(EditorError, self).__init__()
self.reason = reason
def __str__(self):
@ -34,24 +46,27 @@ class GitError(Exception):
"""Unspecified internal error from git.
"""
def __init__(self, command):
super(GitError, self).__init__()
self.command = command
def __str__(self):
return self.command
class ImportError(Exception):
"""An import from a non-Git format cannot be performed.
class UploadError(Exception):
"""A bundle upload to Gerrit did not succeed.
"""
def __init__(self, reason):
super(UploadError, self).__init__()
self.reason = reason
def __str__(self):
return self.reason
class UploadError(Exception):
"""A bundle upload to Gerrit did not succeed.
class DownloadError(Exception):
"""Cannot download a repository.
"""
def __init__(self, reason):
super(DownloadError, self).__init__()
self.reason = reason
def __str__(self):
@ -61,10 +76,24 @@ class NoSuchProjectError(Exception):
"""A specified project does not exist in the work tree.
"""
def __init__(self, name=None):
super(NoSuchProjectError, self).__init__()
self.name = name
def __str__(self):
if self.Name is None:
if self.name is None:
return 'in current directory'
return self.name
class InvalidProjectGroupsError(Exception):
"""A specified project is not suitable for the specified groups
"""
def __init__(self, name=None):
super(InvalidProjectGroupsError, self).__init__()
self.name = name
def __str__(self):
if self.name is None:
return 'in current directory'
return self.name
@ -73,12 +102,12 @@ class RepoChangedException(Exception):
repo or manifest repositories. In this special case we must
use exec to re-execute repo with the new code and manifest.
"""
def __init__(self, extra_args=[]):
self.extra_args = extra_args
def __init__(self, extra_args=None):
super(RepoChangedException, self).__init__()
self.extra_args = extra_args or []
class HookError(Exception):
"""Thrown if a 'repo-hook' could not be run.
The common case is that the file wasn't present when we tried to run it.
"""
pass

View File

@ -13,13 +13,17 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import print_function
import fcntl
import os
import select
import sys
import subprocess
import tempfile
from signal import SIGTERM
from error import GitError
from trace import REPO_TRACE, IsTrace, Trace
from wrapper import Wrapper
GIT = 'git'
MIN_GIT_VERSION = (1, 5, 4)
@ -37,11 +41,11 @@ def ssh_sock(create=True):
if _ssh_sock_path is None:
if not create:
return None
dir = '/tmp'
if not os.path.exists(dir):
dir = tempfile.gettempdir()
tmp_dir = '/tmp'
if not os.path.exists(tmp_dir):
tmp_dir = tempfile.gettempdir()
_ssh_sock_path = os.path.join(
tempfile.mkdtemp('', 'ssh-', dir),
tempfile.mkdtemp('', 'ssh-', tmp_dir),
'master-%r@%h:%p')
return _ssh_sock_path
@ -72,13 +76,38 @@ def terminate_ssh_clients():
pass
_ssh_clients = []
_git_version = None
class _sfd(object):
"""select file descriptor class"""
def __init__(self, fd, dest, std_name):
assert std_name in ('stdout', 'stderr')
self.fd = fd
self.dest = dest
self.std_name = std_name
def fileno(self):
return self.fd.fileno()
class _GitCall(object):
def version(self):
p = GitCommand(None, ['--version'], capture_stdout=True)
if p.Wait() == 0:
return p.stdout
if hasattr(p.stdout, 'decode'):
return p.stdout.decode('utf-8')
else:
return p.stdout
return None
def version_tuple(self):
global _git_version
if _git_version is None:
ver_str = git.version()
_git_version = Wrapper().ParseGitVersion(ver_str)
if _git_version is None:
print('fatal: "%s" unsupported' % ver_str, file=sys.stderr)
sys.exit(1)
return _git_version
def __getattr__(self, name):
name = name.replace('_','-')
def fun(*cmdv):
@ -88,27 +117,13 @@ class _GitCall(object):
return fun
git = _GitCall()
_git_version = None
def git_require(min_version, fail=False):
global _git_version
if _git_version is None:
ver_str = git.version()
if ver_str.startswith('git version '):
_git_version = tuple(
map(lambda x: int(x),
ver_str[len('git version '):].strip().split('.')[0:3]
))
else:
print >>sys.stderr, 'fatal: "%s" unsupported' % ver_str
sys.exit(1)
if min_version <= _git_version:
git_version = git.version_tuple()
if min_version <= git_version:
return True
if fail:
need = '.'.join(map(lambda x: str(x), min_version))
print >>sys.stderr, 'fatal: git %s or later required' % need
need = '.'.join(map(str, min_version))
print('fatal: git %s or later required' % need, file=sys.stderr)
sys.exit(1)
return False
@ -129,21 +144,30 @@ class GitCommand(object):
gitdir = None):
env = os.environ.copy()
for e in [REPO_TRACE,
for key in [REPO_TRACE,
GIT_DIR,
'GIT_ALTERNATE_OBJECT_DIRECTORIES',
'GIT_OBJECT_DIRECTORY',
'GIT_WORK_TREE',
'GIT_GRAFT_FILE',
'GIT_INDEX_FILE']:
if e in env:
del env[e]
if key in env:
del env[key]
# If we are not capturing std* then need to print it.
self.tee = {'stdout': not capture_stdout, 'stderr': not capture_stderr}
if disable_editor:
_setenv(env, 'GIT_EDITOR', ':')
if ssh_proxy:
_setenv(env, 'REPO_SSH_SOCK', ssh_sock())
_setenv(env, 'GIT_SSH', _ssh_proxy())
if 'http_proxy' in env and 'darwin' == sys.platform:
s = "'http.proxy=%s'" % (env['http_proxy'],)
p = env.get('GIT_CONFIG_PARAMETERS')
if p is not None:
s = p + ' ' + s
_setenv(env, 'GIT_CONFIG_PARAMETERS', s)
if project:
if not cwd:
@ -156,22 +180,21 @@ class GitCommand(object):
if gitdir:
_setenv(env, GIT_DIR, gitdir)
cwd = None
command.extend(cmdv)
command.append(cmdv[0])
# Need to use the --progress flag for fetch/clone so output will be
# displayed as by default git only does progress output if stderr is a TTY.
if sys.stderr.isatty() and cmdv[0] in ('fetch', 'clone'):
if '--progress' not in cmdv and '--quiet' not in cmdv:
command.append('--progress')
command.extend(cmdv[1:])
if provide_stdin:
stdin = subprocess.PIPE
else:
stdin = None
if capture_stdout:
stdout = subprocess.PIPE
else:
stdout = None
if capture_stderr:
stderr = subprocess.PIPE
else:
stderr = None
stdout = subprocess.PIPE
stderr = subprocess.PIPE
if IsTrace():
global LAST_CWD
@ -208,7 +231,7 @@ class GitCommand(object):
stdin = stdin,
stdout = stdout,
stderr = stderr)
except Exception, e:
except Exception as e:
raise GitError('%s: %s' % (command[1], e))
if ssh_proxy:
@ -218,26 +241,38 @@ class GitCommand(object):
self.stdin = p.stdin
def Wait(self):
p = self.process
if p.stdin:
p.stdin.close()
self.stdin = None
if p.stdout:
self.stdout = p.stdout.read()
p.stdout.close()
else:
p.stdout = None
if p.stderr:
self.stderr = p.stderr.read()
p.stderr.close()
else:
p.stderr = None
try:
rc = p.wait()
p = self.process
rc = self._CaptureOutput()
finally:
_remove_ssh_client(p)
return rc
def _CaptureOutput(self):
p = self.process
s_in = [_sfd(p.stdout, sys.stdout, 'stdout'),
_sfd(p.stderr, sys.stderr, 'stderr')]
self.stdout = ''
self.stderr = ''
for s in s_in:
flags = fcntl.fcntl(s.fd, fcntl.F_GETFL)
fcntl.fcntl(s.fd, fcntl.F_SETFL, flags | os.O_NONBLOCK)
while s_in:
in_ready, _, _ = select.select(s_in, [], [])
for s in in_ready:
buf = s.fd.read(4096)
if not buf:
s_in.remove(s)
continue
if not hasattr(buf, 'encode'):
buf = buf.decode()
if s.std_name == 'stdout':
self.stdout += buf
else:
self.stderr += buf
if self.tee[s.std_name]:
s.dest.write(buf)
s.dest.flush()
return p.wait()

View File

@ -13,7 +13,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import cPickle
from __future__ import print_function
import json
import os
import re
import subprocess
@ -23,12 +25,25 @@ try:
except ImportError:
import dummy_threading as _threading
import time
import urllib2
from pyversion import is_python3
if is_python3():
import urllib.request
import urllib.error
else:
import urllib2
import imp
urllib = imp.new_module('urllib')
urllib.request = urllib2
urllib.error = urllib2
from signal import SIGTERM
from urllib2 import urlopen, HTTPError
from error import GitError, UploadError
from trace import Trace
if is_python3():
from http.client import HTTPException
else:
from httplib import HTTPException
from git_command import GitCommand
from git_command import ssh_sock
@ -36,7 +51,7 @@ from git_command import terminate_ssh_clients
R_HEADS = 'refs/heads/'
R_TAGS = 'refs/tags/'
ID_RE = re.compile('^[0-9a-f]{40}$')
ID_RE = re.compile(r'^[0-9a-f]{40}$')
REVIEW_CACHE = dict()
@ -57,28 +72,27 @@ class GitConfig(object):
@classmethod
def ForUser(cls):
if cls._ForUser is None:
cls._ForUser = cls(file = os.path.expanduser('~/.gitconfig'))
cls._ForUser = cls(configfile = os.path.expanduser('~/.gitconfig'))
return cls._ForUser
@classmethod
def ForRepository(cls, gitdir, defaults=None):
return cls(file = os.path.join(gitdir, 'config'),
return cls(configfile = os.path.join(gitdir, 'config'),
defaults = defaults)
def __init__(self, file, defaults=None, pickleFile=None):
self.file = file
def __init__(self, configfile, defaults=None, jsonFile=None):
self.file = configfile
self.defaults = defaults
self._cache_dict = None
self._section_dict = None
self._remotes = {}
self._branches = {}
if pickleFile is None:
self._pickle = os.path.join(
self._json = jsonFile
if self._json is None:
self._json = os.path.join(
os.path.dirname(self.file),
'.repopickle_' + os.path.basename(self.file))
else:
self._pickle = pickleFile
'.repo_' + os.path.basename(self.file) + '.json')
def Has(self, name, include_defaults = True):
"""Return true if this configuration file has the key.
@ -105,20 +119,20 @@ class GitConfig(object):
return False
return None
def GetString(self, name, all=False):
def GetString(self, name, all_keys=False):
"""Get the first value for a key, or None if it is not defined.
This configuration file is used first, if the key is not
defined or all = True then the defaults are also searched.
defined or all_keys = True then the defaults are also searched.
"""
try:
v = self._cache[_key(name)]
except KeyError:
if self.defaults:
return self.defaults.GetString(name, all = all)
return self.defaults.GetString(name, all_keys = all_keys)
v = []
if not all:
if not all_keys:
if v:
return v[0]
return None
@ -126,7 +140,7 @@ class GitConfig(object):
r = []
r.extend(v)
if self.defaults:
r.extend(self.defaults.GetString(name, all = True))
r.extend(self.defaults.GetString(name, all_keys = True))
return r
def SetString(self, name, value):
@ -158,7 +172,7 @@ class GitConfig(object):
elif old != value:
self._cache[key] = list(value)
self._do('--replace-all', name, value[0])
for i in xrange(1, len(value)):
for i in range(1, len(value)):
self._do('--add', name, value[i])
elif len(old) != 1 or old[0] != value:
@ -198,6 +212,15 @@ class GitConfig(object):
except KeyError:
return False
def UrlInsteadOf(self, url):
"""Resolve any url.*.insteadof references.
"""
for new_url in self.GetSubSections('url'):
for old_url in self.GetString('url.%s.insteadof' % new_url, True):
if old_url is not None and url.startswith(old_url):
return new_url + url[len(old_url):]
return url
@property
def _sections(self):
d = self._section_dict
@ -224,50 +247,41 @@ class GitConfig(object):
return self._cache_dict
def _Read(self):
d = self._ReadPickle()
d = self._ReadJson()
if d is None:
d = self._ReadGit()
self._SavePickle(d)
self._SaveJson(d)
return d
def _ReadPickle(self):
def _ReadJson(self):
try:
if os.path.getmtime(self._pickle) \
if os.path.getmtime(self._json) \
<= os.path.getmtime(self.file):
os.remove(self._pickle)
os.remove(self._json)
return None
except OSError:
return None
try:
Trace(': unpickle %s', self.file)
fd = open(self._pickle, 'rb')
Trace(': parsing %s', self.file)
fd = open(self._json)
try:
return cPickle.load(fd)
return json.load(fd)
finally:
fd.close()
except EOFError:
os.remove(self._pickle)
return None
except IOError:
os.remove(self._pickle)
return None
except cPickle.PickleError:
os.remove(self._pickle)
except (IOError, ValueError):
os.remove(self._json)
return None
def _SavePickle(self, cache):
def _SaveJson(self, cache):
try:
fd = open(self._pickle, 'wb')
fd = open(self._json, 'w')
try:
cPickle.dump(cache, fd, cPickle.HIGHEST_PROTOCOL)
json.dump(cache, fd, indent=2)
finally:
fd.close()
except IOError:
if os.path.exists(self._pickle):
os.remove(self._pickle)
except cPickle.PickleError:
if os.path.exists(self._pickle):
os.remove(self._pickle)
except (IOError, TypeError):
if os.path.exists(self._json):
os.remove(self._json)
def _ReadGit(self):
"""
@ -280,12 +294,13 @@ class GitConfig(object):
d = self._do('--null', '--list')
if d is None:
return c
for line in d.rstrip('\0').split('\0'):
for line in d.decode('utf-8').rstrip('\0').split('\0'): # pylint: disable=W1401
# Backslash is not anomalous
if '\n' in line:
key, val = line.split('\n', 1)
key, val = line.split('\n', 1)
else:
key = line
val = None
key = line
val = None
if key in c:
c[key].append(val)
@ -410,7 +425,7 @@ def _open_ssh(host, port=None):
'-o','ControlPath %s' % ssh_sock(),
host]
if port is not None:
command_base[1:1] = ['-p',str(port)]
command_base[1:1] = ['-p', str(port)]
# Since the key wasn't in _master_keys, we think that master isn't running.
# ...but before actually starting a master, we'll double-check. This can
@ -441,11 +456,10 @@ def _open_ssh(host, port=None):
try:
Trace(': %s', ' '.join(command))
p = subprocess.Popen(command)
except Exception, e:
except Exception as e:
_ssh_master = False
print >>sys.stderr, \
'\nwarn: cannot enable ssh control master for %s:%s\n%s' \
% (host,port, str(e))
print('\nwarn: cannot enable ssh control master for %s:%s\n%s'
% (host,port, str(e)), file=sys.stderr)
return False
_master_processes.append(p)
@ -480,7 +494,13 @@ def close_ssh():
_master_keys_lock = None
URI_SCP = re.compile(r'^([^@:]*@?[^:/]{1,}):')
URI_ALL = re.compile(r'^([a-z][a-z+]*)://([^@/]*@?[^/]*)/')
URI_ALL = re.compile(r'^([a-z][a-z+-]*)://([^@/]*@?[^/]*)/')
def GetSchemeFromUrl(url):
m = URI_ALL.match(url)
if m:
return m.group(1)
return None
def _preconnect(url):
m = URI_ALL.match(url)
@ -511,9 +531,9 @@ class Remote(object):
self.url = self._Get('url')
self.review = self._Get('review')
self.projectname = self._Get('projectname')
self.fetch = map(lambda x: RefSpec.FromString(x),
self._Get('fetch', all=True))
self._review_protocol = None
self.fetch = list(map(RefSpec.FromString,
self._Get('fetch', all_keys=True)))
self._review_url = None
def _InsteadOf(self):
globCfg = GitConfig.ForUser()
@ -523,7 +543,7 @@ class Remote(object):
for url in urlList:
key = "url." + url + ".insteadOf"
insteadOfList = globCfg.GetString(key, all=True)
insteadOfList = globCfg.GetString(key, all_keys=True)
for insteadOf in insteadOfList:
if self.url.startswith(insteadOf) \
@ -540,75 +560,66 @@ class Remote(object):
connectionUrl = self._InsteadOf()
return _preconnect(connectionUrl)
@property
def ReviewProtocol(self):
if self._review_protocol is None:
def ReviewUrl(self, userEmail):
if self._review_url is None:
if self.review is None:
return None
u = self.review
if not u.startswith('http:') and not u.startswith('https:'):
if u.startswith('persistent-'):
u = u[len('persistent-'):]
if u.split(':')[0] not in ('http', 'https', 'sso'):
u = 'http://%s' % u
if u.endswith('/Gerrit'):
u = u[:len(u) - len('/Gerrit')]
if not u.endswith('/ssh_info'):
if not u.endswith('/'):
u += '/'
u += 'ssh_info'
if u.endswith('/ssh_info'):
u = u[:len(u) - len('/ssh_info')]
if not u.endswith('/'):
u += '/'
http_url = u
if u in REVIEW_CACHE:
info = REVIEW_CACHE[u]
self._review_protocol = info[0]
self._review_host = info[1]
self._review_port = info[2]
self._review_url = REVIEW_CACHE[u]
elif 'REPO_HOST_PORT_INFO' in os.environ:
host, port = os.environ['REPO_HOST_PORT_INFO'].split()
self._review_url = self._SshReviewUrl(userEmail, host, port)
REVIEW_CACHE[u] = self._review_url
elif u.startswith('sso:'):
self._review_url = u # Assume it's right
REVIEW_CACHE[u] = self._review_url
else:
try:
info = urlopen(u).read()
if info == 'NOT_AVAILABLE':
raise UploadError('%s: SSH disabled' % self.review)
if '<' in info:
# Assume the server gave us some sort of HTML
# response back, like maybe a login page.
info_url = u + 'ssh_info'
info = urllib.request.urlopen(info_url).read()
if info == 'NOT_AVAILABLE' or '<' in info:
# If `info` contains '<', we assume the server gave us some sort
# of HTML response back, like maybe a login page.
#
raise UploadError('%s: Cannot parse response' % u)
self._review_protocol = 'ssh'
self._review_host = info.split(" ")[0]
self._review_port = info.split(" ")[1]
except urllib2.URLError, e:
raise UploadError('%s: %s' % (self.review, e.reason[1]))
except HTTPError, e:
if e.code == 404:
self._review_protocol = 'http-post'
self._review_host = None
self._review_port = None
# Assume HTTP if SSH is not enabled or ssh_info doesn't look right.
self._review_url = http_url
else:
raise UploadError('Upload over ssh unavailable')
host, port = info.split()
self._review_url = self._SshReviewUrl(userEmail, host, port)
except urllib.error.HTTPError as e:
raise UploadError('%s: %s' % (self.review, str(e)))
except urllib.error.URLError as e:
raise UploadError('%s: %s' % (self.review, str(e)))
except HTTPException as e:
raise UploadError('%s: %s' % (self.review, e.__class__.__name__))
REVIEW_CACHE[u] = (
self._review_protocol,
self._review_host,
self._review_port)
return self._review_protocol
REVIEW_CACHE[u] = self._review_url
return self._review_url + self.projectname
def SshReviewUrl(self, userEmail):
if self.ReviewProtocol != 'ssh':
return None
def _SshReviewUrl(self, userEmail, host, port):
username = self._config.GetString('review.%s.username' % self.review)
if username is None:
username = userEmail.split("@")[0]
return 'ssh://%s@%s:%s/%s' % (
username,
self._review_host,
self._review_port,
self.projectname)
username = userEmail.split('@')[0]
return 'ssh://%s@%s:%s/' % (username, host, port)
def ToLocal(self, rev):
"""Convert a remote revision string to something we have locally.
"""
if IsId(rev):
return rev
if rev.startswith(R_TAGS):
if self.name == '.' or IsId(rev):
return rev
if not rev.startswith('refs/'):
@ -617,6 +628,10 @@ class Remote(object):
for spec in self.fetch:
if spec.SourceMatches(rev):
return spec.MapSource(rev)
if not rev.startswith(R_HEADS):
return rev
raise GitError('remote %s does not have %s' % (self.name, rev))
def WritesTo(self, ref):
@ -642,15 +657,15 @@ class Remote(object):
self._Set('url', self.url)
self._Set('review', self.review)
self._Set('projectname', self.projectname)
self._Set('fetch', map(lambda x: str(x), self.fetch))
self._Set('fetch', list(map(str, self.fetch)))
def _Set(self, key, value):
key = 'remote.%s.%s' % (self.name, key)
return self._config.SetString(key, value)
def _Get(self, key, all=False):
def _Get(self, key, all_keys=False):
key = 'remote.%s.%s' % (self.name, key)
return self._config.GetString(key, all = all)
return self._config.GetString(key, all_keys = all_keys)
class Branch(object):
@ -686,7 +701,7 @@ class Branch(object):
self._Set('merge', self.merge)
else:
fd = open(self._config.file, 'ab')
fd = open(self._config.file, 'a')
try:
fd.write('[branch "%s"]\n' % self.name)
if self.remote:
@ -700,6 +715,6 @@ class Branch(object):
key = 'branch.%s.%s' % (self.name, key)
return self._config.SetString(key, value)
def _Get(self, key, all=False):
def _Get(self, key, all_keys=False):
key = 'branch.%s.%s' % (self.name, key)
return self._config.GetString(key, all = all)
return self._config.GetString(key, all_keys = all_keys)

View File

@ -14,7 +14,6 @@
# limitations under the License.
import os
import sys
from trace import Trace
HEAD = 'HEAD'
@ -67,7 +66,7 @@ class GitRefs(object):
def _NeedUpdate(self):
Trace(': scan refs %s', self._gitdir)
for name, mtime in self._mtime.iteritems():
for name, mtime in self._mtime.items():
try:
if mtime != os.path.getmtime(os.path.join(self._gitdir, name)):
return True
@ -90,7 +89,7 @@ class GitRefs(object):
attempts = 0
while scan and attempts < 5:
scan_next = {}
for name, dest in scan.iteritems():
for name, dest in scan.items():
if dest in self._phyref:
self._phyref[name] = self._phyref[dest]
else:
@ -101,7 +100,7 @@ class GitRefs(object):
def _ReadPackedRefs(self):
path = os.path.join(self._gitdir, 'packed-refs')
try:
fd = open(path, 'rb')
fd = open(path, 'r')
mtime = os.path.getmtime(path)
except IOError:
return
@ -109,6 +108,7 @@ class GitRefs(object):
return
try:
for line in fd:
line = str(line)
if line[0] == '#':
continue
if line[0] == '^':
@ -116,10 +116,10 @@ class GitRefs(object):
line = line[:-1]
p = line.split(' ')
id = p[0]
ref_id = p[0]
name = p[1]
self._phyref[name] = id
self._phyref[name] = ref_id
finally:
fd.close()
self._mtime['packed-refs'] = mtime
@ -139,22 +139,28 @@ class GitRefs(object):
def _ReadLoose1(self, path, name):
try:
fd = open(path, 'rb')
mtime = os.path.getmtime(path)
except OSError:
return
except IOError:
return
try:
id = fd.readline()
try:
mtime = os.path.getmtime(path)
ref_id = fd.readline()
except (IOError, OSError):
return
finally:
fd.close()
if not id:
try:
ref_id = ref_id.decode()
except AttributeError:
pass
if not ref_id:
return
id = id[:-1]
ref_id = ref_id[:-1]
if id.startswith('ref: '):
self._symref[name] = id[5:]
if ref_id.startswith('ref: '):
self._symref[name] = ref_id[5:]
else:
self._phyref[name] = id
self._phyref[name] = ref_id
self._mtime[name] = mtime

View File

@ -1,5 +1,4 @@
#!/bin/sh
# From Gerrit Code Review 2.1.2-rc2-33-g7e30c72
#
# Part of Gerrit Code Review (http://code.google.com/p/gerrit/)
#
@ -18,77 +17,157 @@
# limitations under the License.
#
unset GREP_OPTIONS
CHANGE_ID_AFTER="Bug|Issue"
MSG="$1"
# Check for, and add if missing, a unique Change-Id
#
add_ChangeId() {
clean_message=$(sed -e '
/^diff --git a\/.*/{
clean_message=`sed -e '
/^diff --git .*/{
s///
q
}
/^Signed-off-by:/d
/^#/d
' "$MSG" | git stripspace)
' "$MSG" | git stripspace`
if test -z "$clean_message"
then
return
fi
if test "false" = "`git config --bool --get gerrit.createChangeId`"
then
return
fi
# Does Change-Id: already exist? if so, exit (no change).
if grep -i '^Change-Id:' "$MSG" >/dev/null
then
return
fi
id=$(_gen_ChangeId)
perl -e '
$MSG = shift;
$id = shift;
$CHANGE_ID_AFTER = shift;
id=`_gen_ChangeId`
T="$MSG.tmp.$$"
AWK=awk
if [ -x /usr/xpg4/bin/awk ]; then
# Solaris AWK is just too broken
AWK=/usr/xpg4/bin/awk
fi
undef $/;
open(I, $MSG); $_ = <I>; close I;
s|^diff --git a/.*||ms;
s|^#.*$||mg;
exit unless $_;
# How this works:
# - parse the commit message as (textLine+ blankLine*)*
# - assume textLine+ to be a footer until proven otherwise
# - exception: the first block is not footer (as it is the title)
# - read textLine+ into a variable
# - then count blankLines
# - once the next textLine appears, print textLine+ blankLine* as these
# aren't footer
# - in END, the last textLine+ block is available for footer parsing
$AWK '
BEGIN {
# while we start with the assumption that textLine+
# is a footer, the first block is not.
isFooter = 0
footerComment = 0
blankLines = 0
}
@message = split /\n/;
$haveFooter = 0;
$startFooter = @message;
for($line = @message - 1; $line >= 0; $line--) {
$_ = $message[$line];
# Skip lines starting with "#" without any spaces before it.
/^#/ { next }
($haveFooter++, next) if /^[a-zA-Z0-9-]+:/;
next if /^[ []/;
$startFooter = $line if ($haveFooter && /^\r?$/);
last;
# Skip the line starting with the diff command and everything after it,
# up to the end of the file, assuming it is only patch data.
# If more than one line before the diff was empty, strip all but one.
/^diff --git / {
blankLines = 0
while (getline) { }
next
}
# Count blank lines outside footer comments
/^$/ && (footerComment == 0) {
blankLines++
next
}
# Catch footer comment
/^\[[a-zA-Z0-9-]+:/ && (isFooter == 1) {
footerComment = 1
}
/]$/ && (footerComment == 1) {
footerComment = 2
}
# We have a non-blank line after blank lines. Handle this.
(blankLines > 0) {
print lines
for (i = 0; i < blankLines; i++) {
print ""
}
@footer = @message[$startFooter+1..@message];
@message = @message[0..$startFooter];
push(@footer, "") unless @footer;
lines = ""
blankLines = 0
isFooter = 1
footerComment = 0
}
for ($line = 0; $line < @footer; $line++) {
$_ = $footer[$line];
next if /^($CHANGE_ID_AFTER):/i;
last;
# Detect that the current block is not the footer
(footerComment == 0) && (!/^\[?[a-zA-Z0-9-]+:/ || /^[a-zA-Z0-9-]+:\/\//) {
isFooter = 0
}
{
# We need this information about the current last comment line
if (footerComment == 2) {
footerComment = 0
}
splice(@footer, $line, 0, "Change-Id: I$id");
if (lines != "") {
lines = lines "\n";
}
lines = lines $0
}
$_ = join("\n", @message, @footer);
open(O, ">$MSG"); print O; close O;
' "$MSG" "$id" "$CHANGE_ID_AFTER"
# Footer handling:
# If the last block is considered a footer, splice in the Change-Id at the
# right place.
# Look for the right place to inject Change-Id by considering
# CHANGE_ID_AFTER. Keys listed in it (case insensitive) come first,
# then Change-Id, then everything else (eg. Signed-off-by:).
#
# Otherwise just print the last block, a new line and the Change-Id as a
# block of its own.
END {
unprinted = 1
if (isFooter == 0) {
print lines "\n"
lines = ""
}
changeIdAfter = "^(" tolower("'"$CHANGE_ID_AFTER"'") "):"
numlines = split(lines, footer, "\n")
for (line = 1; line <= numlines; line++) {
if (unprinted && match(tolower(footer[line]), changeIdAfter) != 1) {
unprinted = 0
print "Change-Id: I'"$id"'"
}
print footer[line]
}
if (unprinted) {
print "Change-Id: I'"$id"'"
}
}' "$MSG" > "$T" && mv "$T" "$MSG" || rm -f "$T"
}
_gen_ChangeIdInput() {
echo "tree $(git write-tree)"
if parent=$(git rev-parse HEAD^0 2>/dev/null)
echo "tree `git write-tree`"
if parent=`git rev-parse "HEAD^0" 2>/dev/null`
then
echo "parent $parent"
fi
echo "author $(git var GIT_AUTHOR_IDENT)"
echo "committer $(git var GIT_COMMITTER_IDENT)"
echo "author `git var GIT_AUTHOR_IDENT`"
echo "committer `git var GIT_COMMITTER_IDENT`"
echo
printf '%s' "$clean_message"
}

View File

@ -35,9 +35,14 @@ elif grep -q "AC Power \+: 1" /proc/pmu/info 2>/dev/null
then
exit 0
elif test -x /usr/bin/pmset && /usr/bin/pmset -g batt |
grep -q "Currently drawing from 'AC Power'"
grep -q "drawing from 'AC Power'"
then
exit 0
elif test -d /sys/bus/acpi/drivers/battery && test 0 = \
"$(find /sys/bus/acpi/drivers/battery/ -type l | wc -l)";
then
# No battery exists.
exit 0
fi
echo "Auto packing deferred; not on AC"

407
main.py
View File

@ -1,4 +1,4 @@
#!/bin/sh
#!/usr/bin/env python
#
# Copyright (C) 2008 The Android Open Source Project
#
@ -14,32 +14,53 @@
# See the License for the specific language governing permissions and
# limitations under the License.
magic='--calling-python-from-/bin/sh--'
"""exec" python -E "$0" "$@" """#$magic"
if __name__ == '__main__':
import sys
if sys.argv[-1] == '#%s' % magic:
del sys.argv[-1]
del magic
from __future__ import print_function
import getpass
import imp
import netrc
import optparse
import os
import re
import sys
import time
from pyversion import is_python3
if is_python3():
import urllib.request
else:
import urllib2
urllib = imp.new_module('urllib')
urllib.request = urllib2
try:
import kerberos
except ImportError:
kerberos = None
from color import SetDefaultColoring
from trace import SetTrace
from git_command import git, GitCommand
from git_config import init_ssh, close_ssh
from command import InteractiveCommand
from command import MirrorSafeCommand
from command import PagedCommand
from subcmds.version import Version
from editor import Editor
from error import DownloadError
from error import InvalidProjectGroupsError
from error import ManifestInvalidRevisionError
from error import ManifestParseError
from error import NoManifestException
from error import NoSuchProjectError
from error import RepoChangedException
from manifest_xml import XmlManifest
from pager import RunPager
from wrapper import WrapperPath, Wrapper
from subcmds import all as all_commands
from subcmds import all_commands
if not is_python3():
# pylint:disable=W0622
input = raw_input
# pylint:enable=W0622
global_options = optparse.OptionParser(
usage="repo [-p|--paginate|--no-pager] COMMAND [ARGS]"
@ -50,9 +71,15 @@ global_options.add_option('-p', '--paginate',
global_options.add_option('--no-pager',
dest='no_pager', action='store_true',
help='disable the pager')
global_options.add_option('--color',
choices=('auto', 'always', 'never'), default=None,
help='control color usage: auto, always, never')
global_options.add_option('--trace',
dest='trace', action='store_true',
help='trace git command execution')
global_options.add_option('--time',
dest='time', action='store_true',
help='time repo command execution')
global_options.add_option('--version',
dest='show_version', action='store_true',
help='display this version of repo')
@ -65,10 +92,11 @@ class _Repo(object):
all_commands['branch'] = all_commands['branches']
def _Run(self, argv):
result = 0
name = None
glob = []
for i in xrange(0, len(argv)):
for i in range(len(argv)):
if not argv[i].startswith('-'):
name = argv[i]
if i > 0:
@ -79,7 +107,7 @@ class _Repo(object):
glob = argv
name = 'help'
argv = []
gopts, gargs = global_options.parse_args(glob)
gopts, _gargs = global_options.parse_args(glob)
if gopts.trace:
SetTrace()
@ -87,28 +115,36 @@ class _Repo(object):
if name == 'help':
name = 'version'
else:
print >>sys.stderr, 'fatal: invalid usage of --version'
sys.exit(1)
print('fatal: invalid usage of --version', file=sys.stderr)
return 1
SetDefaultColoring(gopts.color)
try:
cmd = self.commands[name]
except KeyError:
print >>sys.stderr,\
"repo: '%s' is not a repo command. See 'repo help'."\
% name
sys.exit(1)
print("repo: '%s' is not a repo command. See 'repo help'." % name,
file=sys.stderr)
return 1
cmd.repodir = self.repodir
cmd.manifest = XmlManifest(cmd.repodir)
Editor.globalConfig = cmd.manifest.globalConfig
if not isinstance(cmd, MirrorSafeCommand) and cmd.manifest.IsMirror:
print >>sys.stderr, \
"fatal: '%s' requires a working directory"\
% name
sys.exit(1)
print("fatal: '%s' requires a working directory" % name,
file=sys.stderr)
return 1
copts, cargs = cmd.OptionParser.parse_args(argv)
try:
copts, cargs = cmd.OptionParser.parse_args(argv)
copts = cmd.ReadEnvironmentOptions(copts)
except NoManifestException as e:
print('error: in `%s`: %s' % (' '.join([name] + argv), str(e)),
file=sys.stderr)
print('error: manifest missing or unreadable -- please run init',
file=sys.stderr)
return 1
if not gopts.no_pager and not isinstance(cmd, InteractiveCommand):
config = cmd.manifest.globalConfig
@ -121,68 +157,82 @@ class _Repo(object):
if use_pager:
RunPager(config)
start = time.time()
try:
cmd.Execute(copts, cargs)
except ManifestInvalidRevisionError, e:
print >>sys.stderr, 'error: %s' % str(e)
sys.exit(1)
except NoSuchProjectError, e:
result = cmd.Execute(copts, cargs)
except (DownloadError, ManifestInvalidRevisionError,
NoManifestException) as e:
print('error: in `%s`: %s' % (' '.join([name] + argv), str(e)),
file=sys.stderr)
if isinstance(e, NoManifestException):
print('error: manifest missing or unreadable -- please run init',
file=sys.stderr)
result = 1
except NoSuchProjectError as e:
if e.name:
print >>sys.stderr, 'error: project %s not found' % e.name
print('error: project %s not found' % e.name, file=sys.stderr)
else:
print >>sys.stderr, 'error: no project in current directory'
sys.exit(1)
print('error: no project in current directory', file=sys.stderr)
result = 1
except InvalidProjectGroupsError as e:
if e.name:
print('error: project group must be enabled for project %s' % e.name, file=sys.stderr)
else:
print('error: project group must be enabled for the project in the current directory', file=sys.stderr)
result = 1
finally:
elapsed = time.time() - start
hours, remainder = divmod(elapsed, 3600)
minutes, seconds = divmod(remainder, 60)
if gopts.time:
if hours == 0:
print('real\t%dm%.3fs' % (minutes, seconds), file=sys.stderr)
else:
print('real\t%dh%dm%.3fs' % (hours, minutes, seconds),
file=sys.stderr)
def _MyWrapperPath():
return os.path.join(os.path.dirname(__file__), 'repo')
return result
def _MyRepoPath():
return os.path.dirname(__file__)
def _CurrentWrapperVersion():
VERSION = None
pat = re.compile(r'^VERSION *=')
fd = open(_MyWrapperPath())
for line in fd:
if pat.match(line):
fd.close()
exec line
return VERSION
raise NameError, 'No VERSION in repo script'
def _CheckWrapperVersion(ver, repo_path):
if not repo_path:
repo_path = '~/bin/repo'
if not ver:
print >>sys.stderr, 'no --wrapper-version argument'
sys.exit(1)
print('no --wrapper-version argument', file=sys.stderr)
sys.exit(1)
exp = _CurrentWrapperVersion()
ver = tuple(map(lambda x: int(x), ver.split('.')))
exp = Wrapper().VERSION
ver = tuple(map(int, ver.split('.')))
if len(ver) == 1:
ver = (0, ver[0])
exp_str = '.'.join(map(str, exp))
if exp[0] > ver[0] or ver < (0, 4):
exp_str = '.'.join(map(lambda x: str(x), exp))
print >>sys.stderr, """
print("""
!!! A new repo command (%5s) is available. !!!
!!! You must upgrade before you can continue: !!!
cp %s %s
""" % (exp_str, _MyWrapperPath(), repo_path)
""" % (exp_str, WrapperPath(), repo_path), file=sys.stderr)
sys.exit(1)
if exp > ver:
exp_str = '.'.join(map(lambda x: str(x), exp))
print >>sys.stderr, """
print("""
... A new repo command (%5s) is available.
... You should upgrade soon:
cp %s %s
""" % (exp_str, _MyWrapperPath(), repo_path)
""" % (exp_str, WrapperPath(), repo_path), file=sys.stderr)
def _CheckRepoDir(dir):
if not dir:
print >>sys.stderr, 'no --repo-dir argument'
sys.exit(1)
def _CheckRepoDir(repo_dir):
if not repo_dir:
print('no --repo-dir argument', file=sys.stderr)
sys.exit(1)
def _PruneOptions(argv, opt):
i = 0
@ -199,7 +249,222 @@ def _PruneOptions(argv, opt):
continue
i += 1
_user_agent = None
def _UserAgent():
global _user_agent
if _user_agent is None:
py_version = sys.version_info
os_name = sys.platform
if os_name == 'linux2':
os_name = 'Linux'
elif os_name == 'win32':
os_name = 'Win32'
elif os_name == 'cygwin':
os_name = 'Cygwin'
elif os_name == 'darwin':
os_name = 'Darwin'
p = GitCommand(
None, ['describe', 'HEAD'],
cwd = _MyRepoPath(),
capture_stdout = True)
if p.Wait() == 0:
repo_version = p.stdout
if len(repo_version) > 0 and repo_version[-1] == '\n':
repo_version = repo_version[0:-1]
if len(repo_version) > 0 and repo_version[0] == 'v':
repo_version = repo_version[1:]
else:
repo_version = 'unknown'
_user_agent = 'git-repo/%s (%s) git/%s Python/%d.%d.%d' % (
repo_version,
os_name,
'.'.join(map(str, git.version_tuple())),
py_version[0], py_version[1], py_version[2])
return _user_agent
class _UserAgentHandler(urllib.request.BaseHandler):
def http_request(self, req):
req.add_header('User-Agent', _UserAgent())
return req
def https_request(self, req):
req.add_header('User-Agent', _UserAgent())
return req
def _AddPasswordFromUserInput(handler, msg, req):
# If repo could not find auth info from netrc, try to get it from user input
url = req.get_full_url()
user, password = handler.passwd.find_user_password(None, url)
if user is None:
print(msg)
try:
user = input('User: ')
password = getpass.getpass()
except KeyboardInterrupt:
return
handler.passwd.add_password(None, url, user, password)
class _BasicAuthHandler(urllib.request.HTTPBasicAuthHandler):
def http_error_401(self, req, fp, code, msg, headers):
_AddPasswordFromUserInput(self, msg, req)
return urllib.request.HTTPBasicAuthHandler.http_error_401(
self, req, fp, code, msg, headers)
def http_error_auth_reqed(self, authreq, host, req, headers):
try:
old_add_header = req.add_header
def _add_header(name, val):
val = val.replace('\n', '')
old_add_header(name, val)
req.add_header = _add_header
return urllib.request.AbstractBasicAuthHandler.http_error_auth_reqed(
self, authreq, host, req, headers)
except:
reset = getattr(self, 'reset_retry_count', None)
if reset is not None:
reset()
elif getattr(self, 'retried', None):
self.retried = 0
raise
class _DigestAuthHandler(urllib.request.HTTPDigestAuthHandler):
def http_error_401(self, req, fp, code, msg, headers):
_AddPasswordFromUserInput(self, msg, req)
return urllib.request.HTTPDigestAuthHandler.http_error_401(
self, req, fp, code, msg, headers)
def http_error_auth_reqed(self, auth_header, host, req, headers):
try:
old_add_header = req.add_header
def _add_header(name, val):
val = val.replace('\n', '')
old_add_header(name, val)
req.add_header = _add_header
return urllib.request.AbstractDigestAuthHandler.http_error_auth_reqed(
self, auth_header, host, req, headers)
except:
reset = getattr(self, 'reset_retry_count', None)
if reset is not None:
reset()
elif getattr(self, 'retried', None):
self.retried = 0
raise
class _KerberosAuthHandler(urllib.request.BaseHandler):
def __init__(self):
self.retried = 0
self.context = None
self.handler_order = urllib.request.BaseHandler.handler_order - 50
def http_error_401(self, req, fp, code, msg, headers):
host = req.get_host()
retry = self.http_error_auth_reqed('www-authenticate', host, req, headers)
return retry
def http_error_auth_reqed(self, auth_header, host, req, headers):
try:
spn = "HTTP@%s" % host
authdata = self._negotiate_get_authdata(auth_header, headers)
if self.retried > 3:
raise urllib.request.HTTPError(req.get_full_url(), 401,
"Negotiate auth failed", headers, None)
else:
self.retried += 1
neghdr = self._negotiate_get_svctk(spn, authdata)
if neghdr is None:
return None
req.add_unredirected_header('Authorization', neghdr)
response = self.parent.open(req)
srvauth = self._negotiate_get_authdata(auth_header, response.info())
if self._validate_response(srvauth):
return response
except kerberos.GSSError:
return None
except:
self.reset_retry_count()
raise
finally:
self._clean_context()
def reset_retry_count(self):
self.retried = 0
def _negotiate_get_authdata(self, auth_header, headers):
authhdr = headers.get(auth_header, None)
if authhdr is not None:
for mech_tuple in authhdr.split(","):
mech, __, authdata = mech_tuple.strip().partition(" ")
if mech.lower() == "negotiate":
return authdata.strip()
return None
def _negotiate_get_svctk(self, spn, authdata):
if authdata is None:
return None
result, self.context = kerberos.authGSSClientInit(spn)
if result < kerberos.AUTH_GSS_COMPLETE:
return None
result = kerberos.authGSSClientStep(self.context, authdata)
if result < kerberos.AUTH_GSS_CONTINUE:
return None
response = kerberos.authGSSClientResponse(self.context)
return "Negotiate %s" % response
def _validate_response(self, authdata):
if authdata is None:
return None
result = kerberos.authGSSClientStep(self.context, authdata)
if result == kerberos.AUTH_GSS_COMPLETE:
return True
return None
def _clean_context(self):
if self.context is not None:
kerberos.authGSSClientClean(self.context)
self.context = None
def init_http():
handlers = [_UserAgentHandler()]
mgr = urllib.request.HTTPPasswordMgrWithDefaultRealm()
try:
n = netrc.netrc()
for host in n.hosts:
p = n.hosts[host]
mgr.add_password(p[1], 'http://%s/' % host, p[0], p[2])
mgr.add_password(p[1], 'https://%s/' % host, p[0], p[2])
except netrc.NetrcParseError:
pass
except IOError:
pass
handlers.append(_BasicAuthHandler(mgr))
handlers.append(_DigestAuthHandler(mgr))
if kerberos:
handlers.append(_KerberosAuthHandler())
if 'http_proxy' in os.environ:
url = os.environ['http_proxy']
handlers.append(urllib.request.ProxyHandler({'http': url, 'https': url}))
if 'REPO_CURL_VERBOSE' in os.environ:
handlers.append(urllib.request.HTTPHandler(debuglevel=1))
handlers.append(urllib.request.HTTPSHandler(debuglevel=1))
urllib.request.install_opener(urllib.request.build_opener(*handlers))
def _Main(argv):
result = 0
opt = optparse.OptionParser(usage="repo wrapperinfo -- ...")
opt.add_option("--repo-dir", dest="repodir",
help="path to .repo/")
@ -213,26 +478,36 @@ def _Main(argv):
_CheckWrapperVersion(opt.wrapper_version, opt.wrapper_path)
_CheckRepoDir(opt.repodir)
Version.wrapper_version = opt.wrapper_version
Version.wrapper_path = opt.wrapper_path
repo = _Repo(opt.repodir)
try:
try:
init_ssh()
repo._Run(argv)
init_http()
result = repo._Run(argv) or 0
finally:
close_ssh()
except KeyboardInterrupt:
sys.exit(1)
except RepoChangedException, rce:
print('aborted by user', file=sys.stderr)
result = 1
except ManifestParseError as mpe:
print('fatal: %s' % mpe, file=sys.stderr)
result = 1
except RepoChangedException as rce:
# If repo changed, re-exec ourselves.
#
argv = list(sys.argv)
argv.extend(rce.extra_args)
try:
os.execv(__file__, argv)
except OSError, e:
print >>sys.stderr, 'fatal: cannot restart repo after upgrade'
print >>sys.stderr, 'fatal: %s' % e
sys.exit(128)
except OSError as e:
print('fatal: cannot restart repo after upgrade', file=sys.stderr)
print('fatal: %s' % e, file=sys.stderr)
result = 128
sys.exit(result)
if __name__ == '__main__':
_Main(sys.argv[1:])

View File

@ -13,38 +13,95 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import print_function
import itertools
import os
import re
import sys
import xml.dom.minidom
from git_config import GitConfig, IsId
from project import RemoteSpec, Project, MetaProject, R_HEADS, HEAD
from error import ManifestParseError
from pyversion import is_python3
if is_python3():
import urllib.parse
else:
import imp
import urlparse
urllib = imp.new_module('urllib')
urllib.parse = urlparse
from git_config import GitConfig
from git_refs import R_HEADS, HEAD
from project import RemoteSpec, Project, MetaProject
from error import ManifestParseError, ManifestInvalidRevisionError
MANIFEST_FILE_NAME = 'manifest.xml'
LOCAL_MANIFEST_NAME = 'local_manifest.xml'
LOCAL_MANIFESTS_DIR_NAME = 'local_manifests'
# urljoin gets confused if the scheme is not known.
urllib.parse.uses_relative.extend(['ssh', 'git', 'persistent-https', 'rpc'])
urllib.parse.uses_netloc.extend(['ssh', 'git', 'persistent-https', 'rpc'])
class _Default(object):
"""Project defaults within the manifest."""
revisionExpr = None
destBranchExpr = None
remote = None
sync_j = 1
sync_c = False
sync_s = False
def __eq__(self, other):
return self.__dict__ == other.__dict__
def __ne__(self, other):
return self.__dict__ != other.__dict__
class _XmlRemote(object):
def __init__(self,
name,
alias=None,
fetch=None,
review=None):
manifestUrl=None,
review=None,
revision=None):
self.name = name
self.fetchUrl = fetch
self.manifestUrl = manifestUrl
self.remoteAlias = alias
self.reviewUrl = review
self.revision = revision
self.resolvedFetchUrl = self._resolveFetchUrl()
def __eq__(self, other):
return self.__dict__ == other.__dict__
def __ne__(self, other):
return self.__dict__ != other.__dict__
def _resolveFetchUrl(self):
url = self.fetchUrl.rstrip('/')
manifestUrl = self.manifestUrl.rstrip('/')
# urljoin will gets confused over quite a few things. The ones we care
# about here are:
# * no scheme in the base url, like <hostname:port>
# We handle no scheme by replacing it with an obscure protocol, gopher
# and then replacing it with the original when we are done.
if manifestUrl.find(':') != manifestUrl.find('/') - 1:
url = urllib.parse.urljoin('gopher://' + manifestUrl, url)
url = re.sub(r'^gopher://', '', url)
else:
url = urllib.parse.urljoin(manifestUrl, url)
return url
def ToRemoteSpec(self, projectName):
url = self.fetchUrl
while url.endswith('/'):
url = url[:-1]
url += '/%s.git' % projectName
return RemoteSpec(self.name, url, self.reviewUrl)
url = self.resolvedFetchUrl.rstrip('/') + '/' + projectName
remoteName = self.name
if self.remoteAlias:
remoteName = self.remoteAlias
return RemoteSpec(remoteName, url, self.reviewUrl)
class XmlManifest(object):
"""manages the repo configuration file"""
@ -54,6 +111,7 @@ class XmlManifest(object):
self.topdir = os.path.dirname(self.repodir)
self.manifestFile = os.path.join(self.repodir, MANIFEST_FILE_NAME)
self.globalConfig = GitConfig.ForUser()
self.localManifestWarning = False
self.repoProject = MetaProject(self, 'repo',
gitdir = os.path.join(repodir, 'repo/.git'),
@ -86,23 +144,36 @@ class XmlManifest(object):
self.Override(name)
try:
if os.path.exists(self.manifestFile):
if os.path.lexists(self.manifestFile):
os.remove(self.manifestFile)
os.symlink('manifests/%s' % name, self.manifestFile)
except OSError, e:
raise ManifestParseError('cannot link manifest %s' % name)
except OSError as e:
raise ManifestParseError('cannot link manifest %s: %s' % (name, str(e)))
def _RemoteToXml(self, r, doc, root):
e = doc.createElement('remote')
root.appendChild(e)
e.setAttribute('name', r.name)
e.setAttribute('fetch', r.fetchUrl)
if r.remoteAlias is not None:
e.setAttribute('alias', r.remoteAlias)
if r.reviewUrl is not None:
e.setAttribute('review', r.reviewUrl)
if r.revision is not None:
e.setAttribute('revision', r.revision)
def Save(self, fd, peg_rev=False):
def _ParseGroups(self, groups):
return [x for x in re.split(r'[,\s]+', groups) if x]
def Save(self, fd, peg_rev=False, peg_rev_upstream=True):
"""Write the current manifest out to the given file descriptor.
"""
mp = self.manifestProject
groups = mp.config.GetString('manifest.groups')
if groups:
groups = self._ParseGroups(groups)
doc = xml.dom.minidom.Document()
root = doc.createElement('manifest')
doc.appendChild(root)
@ -117,10 +188,8 @@ class XmlManifest(object):
notice_element.appendChild(doc.createTextNode(indented_notice))
d = self.default
sort_remotes = list(self.remotes.keys())
sort_remotes.sort()
for r in sort_remotes:
for r in sorted(self.remotes):
self._RemoteToXml(self.remotes[r], doc, root)
if self.remotes:
root.appendChild(doc.createTextNode(''))
@ -133,6 +202,15 @@ class XmlManifest(object):
if d.revisionExpr:
have_default = True
e.setAttribute('revision', d.revisionExpr)
if d.sync_j > 1:
have_default = True
e.setAttribute('sync-j', '%d' % d.sync_j)
if d.sync_c:
have_default = True
e.setAttribute('sync-c', 'true')
if d.sync_s:
have_default = True
e.setAttribute('sync-s', 'true')
if have_default:
root.appendChild(e)
root.appendChild(doc.createTextNode(''))
@ -143,27 +221,51 @@ class XmlManifest(object):
root.appendChild(e)
root.appendChild(doc.createTextNode(''))
sort_projects = list(self.projects.keys())
sort_projects.sort()
def output_projects(parent, parent_node, projects):
for project_name in projects:
for project in self._projects[project_name]:
output_project(parent, parent_node, project)
def output_project(parent, parent_node, p):
if not p.MatchesGroups(groups):
return
name = p.name
relpath = p.relpath
if parent:
name = self._UnjoinName(parent.name, name)
relpath = self._UnjoinRelpath(parent.relpath, relpath)
for p in sort_projects:
p = self.projects[p]
e = doc.createElement('project')
root.appendChild(e)
e.setAttribute('name', p.name)
if p.relpath != p.name:
e.setAttribute('path', p.relpath)
if not d.remote or p.remote.name != d.remote.name:
e.setAttribute('remote', p.remote.name)
parent_node.appendChild(e)
e.setAttribute('name', name)
if relpath != name:
e.setAttribute('path', relpath)
remoteName = None
if d.remote:
remoteName = d.remote.remoteAlias or d.remote.name
if not d.remote or p.remote.name != remoteName:
remoteName = p.remote.name
e.setAttribute('remote', remoteName)
if peg_rev:
if self.IsMirror:
e.setAttribute('revision',
p.bare_git.rev_parse(p.revisionExpr + '^0'))
value = p.bare_git.rev_parse(p.revisionExpr + '^0')
else:
e.setAttribute('revision',
p.work_git.rev_parse(HEAD + '^0'))
elif not d.revisionExpr or p.revisionExpr != d.revisionExpr:
e.setAttribute('revision', p.revisionExpr)
value = p.work_git.rev_parse(HEAD + '^0')
e.setAttribute('revision', value)
if peg_rev_upstream:
if p.upstream:
e.setAttribute('upstream', p.upstream)
elif value != p.revisionExpr:
# Only save the origin if the origin is not a sha1, and the default
# isn't our value
e.setAttribute('upstream', p.revisionExpr)
else:
revision = self.remotes[remoteName].revision or d.revisionExpr
if not revision or revision != p.revisionExpr:
e.setAttribute('revision', p.revisionExpr)
if p.upstream and p.upstream != p.revisionExpr:
e.setAttribute('upstream', p.upstream)
for c in p.copyfiles:
ce = doc.createElement('copyfile')
@ -171,6 +273,37 @@ class XmlManifest(object):
ce.setAttribute('dest', c.dest)
e.appendChild(ce)
for l in p.linkfiles:
le = doc.createElement('linkfile')
le.setAttribute('src', l.src)
le.setAttribute('dest', l.dest)
e.appendChild(le)
default_groups = ['all', 'name:%s' % p.name, 'path:%s' % p.relpath]
egroups = [g for g in p.groups if g not in default_groups]
if egroups:
e.setAttribute('groups', ','.join(egroups))
for a in p.annotations:
if a.keep == "true":
ae = doc.createElement('annotation')
ae.setAttribute('name', a.name)
ae.setAttribute('value', a.value)
e.appendChild(ae)
if p.sync_c:
e.setAttribute('sync-c', 'true')
if p.sync_s:
e.setAttribute('sync-s', 'true')
if p.subprojects:
subprojects = set(subp.name for subp in p.subprojects)
output_projects(p, e, list(sorted(subprojects)))
projects = set(p.name for p in self._paths.values() if not p.parent)
output_projects(None, root, list(sorted(projects)))
if self._repo_hooks_project:
root.appendChild(doc.createTextNode(''))
e = doc.createElement('repo-hooks')
@ -181,10 +314,15 @@ class XmlManifest(object):
doc.writexml(fd, '', ' ', '\n', 'UTF-8')
@property
def paths(self):
self._Load()
return self._paths
@property
def projects(self):
self._Load()
return self._projects
return list(self._paths.values())
@property
def remotes(self):
@ -215,9 +353,14 @@ class XmlManifest(object):
def IsMirror(self):
return self.manifestProject.config.GetBoolean('repo.mirror')
@property
def IsArchive(self):
return self.manifestProject.config.GetBoolean('repo.archive')
def _Unload(self):
self._loaded = False
self._projects = {}
self._paths = {}
self._remotes = {}
self._default = None
self._repo_hooks_project = None
@ -233,16 +376,35 @@ class XmlManifest(object):
b = b[len(R_HEADS):]
self.branch = b
self._ParseManifest(True)
nodes = []
nodes.append(self._ParseManifestXml(self.manifestFile,
self.manifestProject.worktree))
local = os.path.join(self.repodir, LOCAL_MANIFEST_NAME)
if os.path.exists(local):
try:
real = self.manifestFile
self.manifestFile = local
self._ParseManifest(False)
finally:
self.manifestFile = real
if not self.localManifestWarning:
self.localManifestWarning = True
print('warning: %s is deprecated; put local manifests in `%s` instead'
% (LOCAL_MANIFEST_NAME, os.path.join(self.repodir, LOCAL_MANIFESTS_DIR_NAME)),
file=sys.stderr)
nodes.append(self._ParseManifestXml(local, self.repodir))
local_dir = os.path.abspath(os.path.join(self.repodir, LOCAL_MANIFESTS_DIR_NAME))
try:
for local_file in sorted(os.listdir(local_dir)):
if local_file.endswith('.xml'):
local = os.path.join(local_dir, local_file)
nodes.append(self._ParseManifestXml(local, self.repodir))
except OSError:
pass
try:
self._ParseManifest(nodes)
except ManifestParseError as e:
# There was a problem parsing, unload ourselves in case they catch
# this error and try again later, we will show the correct error
self._Unload()
raise e
if self.IsMirror:
self._AddMetaProjectMirror(self.repoProject)
@ -250,54 +412,69 @@ class XmlManifest(object):
self._loaded = True
def _ParseManifest(self, is_root_file):
root = xml.dom.minidom.parse(self.manifestFile)
def _ParseManifestXml(self, path, include_root):
try:
root = xml.dom.minidom.parse(path)
except (OSError, xml.parsers.expat.ExpatError) as e:
raise ManifestParseError("error parsing manifest %s: %s" % (path, e))
if not root or not root.childNodes:
raise ManifestParseError(
"no root node in %s" %
self.manifestFile)
raise ManifestParseError("no root node in %s" % (path,))
config = root.childNodes[0]
if config.nodeName != 'manifest':
raise ManifestParseError(
"no <manifest> in %s" %
self.manifestFile)
for manifest in root.childNodes:
if manifest.nodeName == 'manifest':
break
else:
raise ManifestParseError("no <manifest> in %s" % (path,))
for node in config.childNodes:
if node.nodeName == 'remove-project':
nodes = []
for node in manifest.childNodes: # pylint:disable=W0631
# We only get here if manifest is initialised
if node.nodeName == 'include':
name = self._reqatt(node, 'name')
fp = os.path.join(include_root, name)
if not os.path.isfile(fp):
raise ManifestParseError("include %s doesn't exist or isn't a file"
% (name,))
try:
del self._projects[name]
except KeyError:
nodes.extend(self._ParseManifestXml(fp, include_root))
# should isolate this to the exact exception, but that's
# tricky. actual parsing implementation may vary.
except (KeyboardInterrupt, RuntimeError, SystemExit):
raise
except Exception as e:
raise ManifestParseError(
'project %s not found' %
(name))
"failed parsing included manifest %s: %s", (name, e))
else:
nodes.append(node)
return nodes
# If the manifest removes the hooks project, treat it as if it deleted
# the repo-hooks element too.
if self._repo_hooks_project and (self._repo_hooks_project.name == name):
self._repo_hooks_project = None
for node in config.childNodes:
def _ParseManifest(self, node_list):
for node in itertools.chain(*node_list):
if node.nodeName == 'remote':
remote = self._ParseRemote(node)
if self._remotes.get(remote.name):
raise ManifestParseError(
'duplicate remote %s in %s' %
(remote.name, self.manifestFile))
self._remotes[remote.name] = remote
if remote:
if remote.name in self._remotes:
if remote != self._remotes[remote.name]:
raise ManifestParseError(
'remote %s already exists with different attributes' %
(remote.name))
else:
self._remotes[remote.name] = remote
for node in config.childNodes:
for node in itertools.chain(*node_list):
if node.nodeName == 'default':
if self._default is not None:
raise ManifestParseError(
'duplicate default in %s' %
(self.manifestFile))
self._default = self._ParseDefault(node)
new_default = self._ParseDefault(node)
if self._default is None:
self._default = new_default
elif new_default != self._default:
raise ManifestParseError('duplicate default in %s' %
(self.manifestFile))
if self._default is None:
self._default = _Default()
for node in config.childNodes:
for node in itertools.chain(*node_list):
if node.nodeName == 'notice':
if self._notice is not None:
raise ManifestParseError(
@ -305,25 +482,51 @@ class XmlManifest(object):
(self.manifestFile))
self._notice = self._ParseNotice(node)
for node in config.childNodes:
for node in itertools.chain(*node_list):
if node.nodeName == 'manifest-server':
url = self._reqatt(node, 'url')
if self._manifest_server is not None:
raise ManifestParseError(
'duplicate manifest-server in %s' %
(self.manifestFile))
raise ManifestParseError(
'duplicate manifest-server in %s' %
(self.manifestFile))
self._manifest_server = url
for node in config.childNodes:
def recursively_add_projects(project):
projects = self._projects.setdefault(project.name, [])
if project.relpath is None:
raise ManifestParseError(
'missing path for %s in %s' %
(project.name, self.manifestFile))
if project.relpath in self._paths:
raise ManifestParseError(
'duplicate path %s in %s' %
(project.relpath, self.manifestFile))
self._paths[project.relpath] = project
projects.append(project)
for subproject in project.subprojects:
recursively_add_projects(subproject)
for node in itertools.chain(*node_list):
if node.nodeName == 'project':
project = self._ParseProject(node)
if self._projects.get(project.name):
raise ManifestParseError(
'duplicate project %s in %s' %
(project.name, self.manifestFile))
self._projects[project.name] = project
recursively_add_projects(project)
if node.nodeName == 'extend-project':
name = self._reqatt(node, 'name')
for node in config.childNodes:
if name not in self._projects:
raise ManifestParseError('extend-project element specifies non-existent '
'project: %s' % name)
path = node.getAttribute('path')
groups = node.getAttribute('groups')
if groups:
groups = self._ParseGroups(groups)
for p in self._projects[name]:
if path and p.relpath != path:
continue
if groups:
p.groups.extend(groups)
if node.nodeName == 'repo-hooks':
# Get the name of the project and the (space-separated) list of enabled.
repo_hooks_project = self._reqatt(node, 'in-project')
@ -337,23 +540,45 @@ class XmlManifest(object):
# Store a reference to the Project.
try:
self._repo_hooks_project = self._projects[repo_hooks_project]
repo_hooks_projects = self._projects[repo_hooks_project]
except KeyError:
raise ManifestParseError(
'project %s not found for repo-hooks' %
(repo_hooks_project))
if len(repo_hooks_projects) != 1:
raise ManifestParseError(
'internal error parsing repo-hooks in %s' %
(self.manifestFile))
self._repo_hooks_project = repo_hooks_projects[0]
# Store the enabled hooks in the Project object.
self._repo_hooks_project.enabled_repo_hooks = enabled_repo_hooks
if node.nodeName == 'remove-project':
name = self._reqatt(node, 'name')
if name not in self._projects:
raise ManifestParseError('remove-project element specifies non-existent '
'project: %s' % name)
for p in self._projects[name]:
del self._paths[p.relpath]
del self._projects[name]
# If the manifest removes the hooks project, treat it as if it deleted
# the repo-hooks element too.
if self._repo_hooks_project and (self._repo_hooks_project.name == name):
self._repo_hooks_project = None
def _AddMetaProjectMirror(self, m):
name = None
m_url = m.GetRemote(m.remote.name).url
if m_url.endswith('/.git'):
raise ManifestParseError, 'refusing to mirror %s' % m_url
raise ManifestParseError('refusing to mirror %s' % m_url)
if self._default and self._default.remote:
url = self._default.remote.fetchUrl
url = self._default.remote.resolvedFetchUrl
if not url.endswith('/'):
url += '/'
if m_url.startswith(url):
@ -362,7 +587,8 @@ class XmlManifest(object):
if name is None:
s = m_url.rindex('/') + 1
remote = _XmlRemote('origin', m_url[:s])
manifestUrl = self.manifestProject.config.GetString('remote.origin.url')
remote = _XmlRemote('origin', fetch=m_url[:s], manifestUrl=manifestUrl)
name = m_url[s:]
if name.endswith('.git'):
@ -375,22 +601,31 @@ class XmlManifest(object):
name = name,
remote = remote.ToRemoteSpec(name),
gitdir = gitdir,
objdir = gitdir,
worktree = None,
relpath = None,
relpath = name or None,
revisionExpr = m.revisionExpr,
revisionId = None)
self._projects[project.name] = project
self._projects[project.name] = [project]
self._paths[project.relpath] = project
def _ParseRemote(self, node):
"""
reads a <remote> element from the manifest file
"""
name = self._reqatt(node, 'name')
alias = node.getAttribute('alias')
if alias == '':
alias = None
fetch = self._reqatt(node, 'fetch')
review = node.getAttribute('review')
if review == '':
review = None
return _XmlRemote(name, fetch, review)
revision = node.getAttribute('revision')
if revision == '':
revision = None
manifestUrl = self.manifestProject.config.GetString('remote.origin.url')
return _XmlRemote(name, alias, fetch, manifestUrl, review, revision)
def _ParseDefault(self, node):
"""
@ -401,6 +636,26 @@ class XmlManifest(object):
d.revisionExpr = node.getAttribute('revision')
if d.revisionExpr == '':
d.revisionExpr = None
d.destBranchExpr = node.getAttribute('dest-branch') or None
sync_j = node.getAttribute('sync-j')
if sync_j == '' or sync_j is None:
d.sync_j = 1
else:
d.sync_j = int(sync_j)
sync_c = node.getAttribute('sync-c')
if not sync_c:
d.sync_c = False
else:
d.sync_c = sync_c.lower() in ("yes", "true", "1")
sync_s = node.getAttribute('sync-s')
if not sync_s:
d.sync_s = False
else:
d.sync_s = sync_s.lower() in ("yes", "true", "1")
return d
def _ParseNotice(self, node):
@ -421,7 +676,7 @@ class XmlManifest(object):
# Figure out minimum indentation, skipping the first line (the same line
# as the <notice> tag)...
minIndent = sys.maxint
minIndent = sys.maxsize
lines = notice.splitlines()
for line in lines[1:]:
lstrippedLine = line.lstrip()
@ -442,59 +697,155 @@ class XmlManifest(object):
return '\n'.join(cleanLines)
def _ParseProject(self, node):
def _JoinName(self, parent_name, name):
return os.path.join(parent_name, name)
def _UnjoinName(self, parent_name, name):
return os.path.relpath(name, parent_name)
def _ParseProject(self, node, parent = None):
"""
reads a <project> element from the manifest file
"""
name = self._reqatt(node, 'name')
if parent:
name = self._JoinName(parent.name, name)
remote = self._get_remote(node)
if remote is None:
remote = self._default.remote
if remote is None:
raise ManifestParseError, \
"no remote for project %s within %s" % \
(name, self.manifestFile)
raise ManifestParseError("no remote for project %s within %s" %
(name, self.manifestFile))
revisionExpr = node.getAttribute('revision')
revisionExpr = node.getAttribute('revision') or remote.revision
if not revisionExpr:
revisionExpr = self._default.revisionExpr
if not revisionExpr:
raise ManifestParseError, \
"no revision for project %s within %s" % \
(name, self.manifestFile)
raise ManifestParseError("no revision for project %s within %s" %
(name, self.manifestFile))
path = node.getAttribute('path')
if not path:
path = name
if path.startswith('/'):
raise ManifestParseError, \
"project %s path cannot be absolute in %s" % \
(name, self.manifestFile)
raise ManifestParseError("project %s path cannot be absolute in %s" %
(name, self.manifestFile))
if self.IsMirror:
relpath = None
worktree = None
gitdir = os.path.join(self.topdir, '%s.git' % name)
rebase = node.getAttribute('rebase')
if not rebase:
rebase = True
else:
worktree = os.path.join(self.topdir, path).replace('\\', '/')
gitdir = os.path.join(self.repodir, 'projects/%s.git' % path)
rebase = rebase.lower() in ("yes", "true", "1")
sync_c = node.getAttribute('sync-c')
if not sync_c:
sync_c = False
else:
sync_c = sync_c.lower() in ("yes", "true", "1")
sync_s = node.getAttribute('sync-s')
if not sync_s:
sync_s = self._default.sync_s
else:
sync_s = sync_s.lower() in ("yes", "true", "1")
clone_depth = node.getAttribute('clone-depth')
if clone_depth:
try:
clone_depth = int(clone_depth)
if clone_depth <= 0:
raise ValueError()
except ValueError:
raise ManifestParseError('invalid clone-depth %s in %s' %
(clone_depth, self.manifestFile))
dest_branch = node.getAttribute('dest-branch') or self._default.destBranchExpr
upstream = node.getAttribute('upstream')
groups = ''
if node.hasAttribute('groups'):
groups = node.getAttribute('groups')
groups = self._ParseGroups(groups)
if parent is None:
relpath, worktree, gitdir, objdir = self.GetProjectPaths(name, path)
else:
relpath, worktree, gitdir, objdir = \
self.GetSubprojectPaths(parent, name, path)
default_groups = ['all', 'name:%s' % name, 'path:%s' % relpath]
groups.extend(set(default_groups).difference(groups))
if self.IsMirror and node.hasAttribute('force-path'):
if node.getAttribute('force-path').lower() in ("yes", "true", "1"):
gitdir = os.path.join(self.topdir, '%s.git' % path)
project = Project(manifest = self,
name = name,
remote = remote.ToRemoteSpec(name),
gitdir = gitdir,
objdir = objdir,
worktree = worktree,
relpath = path,
relpath = relpath,
revisionExpr = revisionExpr,
revisionId = None)
revisionId = None,
rebase = rebase,
groups = groups,
sync_c = sync_c,
sync_s = sync_s,
clone_depth = clone_depth,
upstream = upstream,
parent = parent,
dest_branch = dest_branch)
for n in node.childNodes:
if n.nodeName == 'copyfile':
self._ParseCopyFile(project, n)
if n.nodeName == 'linkfile':
self._ParseLinkFile(project, n)
if n.nodeName == 'annotation':
self._ParseAnnotation(project, n)
if n.nodeName == 'project':
project.subprojects.append(self._ParseProject(n, parent = project))
return project
def GetProjectPaths(self, name, path):
relpath = path
if self.IsMirror:
worktree = None
gitdir = os.path.join(self.topdir, '%s.git' % name)
objdir = gitdir
else:
worktree = os.path.join(self.topdir, path).replace('\\', '/')
gitdir = os.path.join(self.repodir, 'projects', '%s.git' % path)
objdir = os.path.join(self.repodir, 'project-objects', '%s.git' % name)
return relpath, worktree, gitdir, objdir
def GetProjectsWithName(self, name):
return self._projects.get(name, [])
def GetSubprojectName(self, parent, submodule_path):
return os.path.join(parent.name, submodule_path)
def _JoinRelpath(self, parent_relpath, relpath):
return os.path.join(parent_relpath, relpath)
def _UnjoinRelpath(self, parent_relpath, relpath):
return os.path.relpath(relpath, parent_relpath)
def GetSubprojectPaths(self, parent, name, path):
relpath = self._JoinRelpath(parent.relpath, path)
gitdir = os.path.join(parent.gitdir, 'subprojects', '%s.git' % path)
objdir = os.path.join(parent.gitdir, 'subproject-objects', '%s.git' % name)
if self.IsMirror:
worktree = None
else:
worktree = os.path.join(parent.worktree, path).replace('\\', '/')
return relpath, worktree, gitdir, objdir
def _ParseCopyFile(self, project, node):
src = self._reqatt(node, 'src')
dest = self._reqatt(node, 'dest')
@ -503,6 +854,26 @@ class XmlManifest(object):
# dest is relative to the top of the tree
project.AddCopyFile(src, dest, os.path.join(self.topdir, dest))
def _ParseLinkFile(self, project, node):
src = self._reqatt(node, 'src')
dest = self._reqatt(node, 'dest')
if not self.IsMirror:
# src is project relative;
# dest is relative to the top of the tree
project.AddLinkFile(src, dest, os.path.join(self.topdir, dest))
def _ParseAnnotation(self, project, node):
name = self._reqatt(node, 'name')
value = self._reqatt(node, 'value')
try:
keep = self._reqatt(node, 'keep').lower()
except ManifestParseError:
keep = "true"
if keep != "true" and keep != "false":
raise ManifestParseError('optional "keep" attribute must be '
'"true" or "false"')
project.AddAnnotation(name, value, keep)
def _get_remote(self, node):
name = node.getAttribute('remote')
if not name:
@ -510,9 +881,8 @@ class XmlManifest(object):
v = self._remotes.get(name)
if not v:
raise ManifestParseError, \
"remote %s not defined in %s" % \
(name, self.manifestFile)
raise ManifestParseError("remote %s not defined in %s" %
(name, self.manifestFile))
return v
def _reqatt(self, node, attname):
@ -521,7 +891,41 @@ class XmlManifest(object):
"""
v = node.getAttribute(attname)
if not v:
raise ManifestParseError, \
"no %s in <%s> within %s" % \
(attname, node.nodeName, self.manifestFile)
raise ManifestParseError("no %s in <%s> within %s" %
(attname, node.nodeName, self.manifestFile))
return v
def projectsDiff(self, manifest):
"""return the projects differences between two manifests.
The diff will be from self to given manifest.
"""
fromProjects = self.paths
toProjects = manifest.paths
fromKeys = sorted(fromProjects.keys())
toKeys = sorted(toProjects.keys())
diff = {'added': [], 'removed': [], 'changed': [], 'unreachable': []}
for proj in fromKeys:
if not proj in toKeys:
diff['removed'].append(fromProjects[proj])
else:
fromProj = fromProjects[proj]
toProj = toProjects[proj]
try:
fromRevId = fromProj.GetCommitRevisionId()
toRevId = toProj.GetCommitRevisionId()
except ManifestInvalidRevisionError:
diff['unreachable'].append((fromProj, toProj))
else:
if fromRevId != toRevId:
diff['changed'].append((fromProj, toProj))
toKeys.remove(proj)
for proj in toKeys:
diff['added'].append(toProjects[proj])
return diff

View File

@ -13,6 +13,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import print_function
import os
import select
import sys
@ -49,8 +50,8 @@ def RunPager(globalConfig):
_BecomePager(pager)
except Exception:
print >>sys.stderr, "fatal: cannot start pager '%s'" % pager
os.exit(255)
print("fatal: cannot start pager '%s'" % pager, file=sys.stderr)
sys.exit(255)
def _SelectPager(globalConfig):
try:
@ -74,11 +75,11 @@ def _BecomePager(pager):
# ready works around a long-standing bug in popularly
# available versions of 'less', a better 'more'.
#
a, b, c = select.select([0], [], [0])
_a, _b, _c = select.select([0], [], [0])
os.environ['LESS'] = 'FRSX'
try:
os.execvp(pager, [pager])
except OSError, e:
except OSError:
os.execv('/bin/sh', ['sh', '-c', pager])

View File

@ -21,13 +21,14 @@ from trace import IsTrace
_NOT_TTY = not os.isatty(2)
class Progress(object):
def __init__(self, title, total=0):
def __init__(self, title, total=0, units=''):
self._title = title
self._total = total
self._done = 0
self._lastp = -1
self._start = time()
self._show = False
self._units = units
def update(self, inc=1):
self._done += inc
@ -51,11 +52,11 @@ class Progress(object):
if self._lastp != p:
self._lastp = p
sys.stderr.write('\r%s: %3d%% (%d/%d) ' % (
sys.stderr.write('\r%s: %3d%% (%d%s/%d%s) ' % (
self._title,
p,
self._done,
self._total))
self._done, self._units,
self._total, self._units))
sys.stderr.flush()
def end(self):
@ -69,9 +70,9 @@ class Progress(object):
sys.stderr.flush()
else:
p = (100 * self._done) / self._total
sys.stderr.write('\r%s: %3d%% (%d/%d), done. \n' % (
sys.stderr.write('\r%s: %3d%% (%d%s/%d%s), done. \n' % (
self._title,
p,
self._done,
self._total))
self._done, self._units,
self._total, self._units))
sys.stderr.flush()

1543
project.py

File diff suppressed because it is too large Load Diff

19
pyversion.py Normal file
View File

@ -0,0 +1,19 @@
#
# Copyright (C) 2013 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import sys
def is_python3():
return sys.version_info[0] == 3

440
repo
View File

@ -1,9 +1,9 @@
#!/bin/sh
#!/usr/bin/env python
## repo default configuration
##
REPO_URL='git://android.git.kernel.org/tools/repo.git'
REPO_REV='stable'
REPO_URL = 'https://gerrit.googlesource.com/git-repo'
REPO_REV = 'stable'
# Copyright (C) 2008 Google Inc.
#
@ -19,19 +19,11 @@ REPO_REV='stable'
# See the License for the specific language governing permissions and
# limitations under the License.
magic='--calling-python-from-/bin/sh--'
"""exec" python -E "$0" "$@" """#$magic"
if __name__ == '__main__':
import sys
if sys.argv[-1] == '#%s' % magic:
del sys.argv[-1]
del magic
# increment this whenever we make important changes to this script
VERSION = (1, 10)
VERSION = (1, 21)
# increment this if the MAINTAINER_KEYS block is modified
KEYRING_VERSION = (1,0)
KEYRING_VERSION = (1, 2)
MAINTAINER_KEYS = """
Repo Maintainer <repo@android.kernel.org>
@ -74,24 +66,85 @@ HTHs37+/QLMomGEGKZMWi0dShU2J5mNRQu3Hhxl3hHDVbt5CeJBb26aQcQrFz69W
zE3GNvmJosh6leayjtI9P2A6iEkEGBECAAkFAkj3uiACGwwACgkQFlMNXpIPXGWp
TACbBS+Up3RpfYVfd63c1cDdlru13pQAn3NQy/SN858MkxN+zym86UBgOad2
=CMiZ
-----END PGP PUBLIC KEY BLOCK-----
Conley Owens <cco3@android.com>
-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: GnuPG v1.4.11 (GNU/Linux)
mQENBFHRvc8BCADFg45Xx/y6QDC+T7Y/gGc7vx0ww7qfOwIKlAZ9xG3qKunMxo+S
hPCnzEl3cq+6I1Ww/ndop/HB3N3toPXRCoN8Vs4/Hc7by+SnaLFnacrm+tV5/OgT
V37Lzt8lhay1Kl+YfpFwHYYpIEBLFV9knyfRXS/428W2qhdzYfvB15/AasRmwmor
py4NIzSs8UD/SPr1ihqNCdZM76+MQyN5HMYXW/ALZXUFG0pwluHFA7hrfPG74i8C
zMiP7qvMWIl/r/jtzHioH1dRKgbod+LZsrDJ8mBaqsZaDmNJMhss9g76XvfMyLra
9DI9/iFuBpGzeqBv0hwOGQspLRrEoyTeR6n1ABEBAAG0H0NvbmxleSBPd2VucyA8
Y2NvM0BhbmRyb2lkLmNvbT6JATgEEwECACIFAlHRvc8CGwMGCwkIBwMCBhUIAgkK
CwQWAgMBAh4BAheAAAoJEGe35EhpKzgsP6AIAJKJmNtn4l7hkYHKHFSo3egb6RjQ
zEIP3MFTcu8HFX1kF1ZFbrp7xqurLaE53kEkKuAAvjJDAgI8mcZHP1JyplubqjQA
xvv84gK+OGP3Xk+QK1ZjUQSbjOpjEiSZpRhWcHci3dgOUH4blJfByHw25hlgHowd
a/2PrNKZVcJ92YienaxxGjcXEUcd0uYEG2+rwllQigFcnMFDhr9B71MfalRHjFKE
fmdoypqLrri61YBc59P88Rw2/WUpTQjgNubSqa3A2+CKdaRyaRw+2fdF4TdR0h8W
zbg+lbaPtJHsV+3mJC7fq26MiJDRJa5ZztpMn8su20gbLgi2ShBOaHAYDDi5AQ0E
UdG9zwEIAMoOBq+QLNozAhxOOl5GL3StTStGRgPRXINfmViTsihrqGCWBBUfXlUE
OytC0mYcrDUQev/8ToVoyqw+iGSwDkcSXkrEUCKFtHV/GECWtk1keyHgR10YKI1R
mquSXoubWGqPeG1PAI74XWaRx8UrL8uCXUtmD8Q5J7mDjKR5NpxaXrwlA0bKsf2E
Gp9tu1kKauuToZhWHMRMqYSOGikQJwWSFYKT1KdNcOXLQF6+bfoJ6sjVYdwfmNQL
Ixn8QVhoTDedcqClSWB17VDEFDFa7MmqXZz2qtM3X1R/MUMHqPtegQzBGNhRdnI2
V45+1Nnx/uuCxDbeI4RbHzujnxDiq70AEQEAAYkBHwQYAQIACQUCUdG9zwIbDAAK
CRBnt+RIaSs4LNVeB/0Y2pZ8I7gAAcEM0Xw8drr4omg2fUoK1J33ozlA/RxeA/lJ
I3KnyCDTpXuIeBKPGkdL8uMATC9Z8DnBBajRlftNDVZS3Hz4G09G9QpMojvJkFJV
By+01Flw/X+eeN8NpqSuLV4W+AjEO8at/VvgKr1AFvBRdZ7GkpI1o6DgPe7ZqX+1
dzQZt3e13W0rVBb/bUgx9iSLoeWP3aq/k+/GRGOR+S6F6BBSl0SQ2EF2+dIywb1x
JuinEP+AwLAUZ1Bsx9ISC0Agpk2VeHXPL3FGhroEmoMvBzO0kTFGyoeT7PR/BfKv
+H/g3HsL2LOB9uoIm8/5p2TTU5ttYCXMHhQZ81AY
=AUp4
-----END PGP PUBLIC KEY BLOCK-----
"""
GIT = 'git' # our git command
MIN_GIT_VERSION = (1, 5, 4) # minimum supported git version
MIN_GIT_VERSION = (1, 7, 2) # minimum supported git version
repodir = '.repo' # name of repo's private directory
S_repo = 'repo' # special repo reposiory
S_repo = 'repo' # special repo repository
S_manifests = 'manifests' # special manifest repository
REPO_MAIN = S_repo + '/main.py' # main script
MIN_PYTHON_VERSION = (2, 6) # minimum supported python version
import errno
import optparse
import os
import re
import readline
import shutil
import stat
import subprocess
import sys
if sys.version_info[0] == 3:
import urllib.request
import urllib.error
else:
import imp
import urllib2
urllib = imp.new_module('urllib')
urllib.request = urllib2
urllib.error = urllib2
def _print(*objects, **kwargs):
sep = kwargs.get('sep', ' ')
end = kwargs.get('end', '\n')
out = kwargs.get('file', sys.stdout)
out.write(sep.join(objects) + end)
# Python version check
ver = sys.version_info
if (ver[0], ver[1]) < MIN_PYTHON_VERSION:
_print('error: Python version %s unsupported.\n'
'Please use Python 2.6 - 2.7 instead.'
% sys.version.split(' ')[0], file=sys.stderr)
sys.exit(1)
home_dot_repo = os.path.expanduser('~/.repoconfig')
gpg_dir = os.path.join(home_dot_repo, 'gnupg')
@ -117,10 +170,29 @@ group.add_option('-m', '--manifest-name',
help='initial manifest file', metavar='NAME.xml')
group.add_option('--mirror',
dest='mirror', action='store_true',
help='mirror the forrest')
help='create a replica of the remote repositories '
'rather than a client working directory')
group.add_option('--reference',
dest='reference',
help='location of mirror directory', metavar='DIR')
group.add_option('--depth', type='int', default=None,
dest='depth',
help='create a shallow clone with given depth; see git clone')
group.add_option('--archive',
dest='archive', action='store_true',
help='checkout an archive instead of a git repository for '
'each project. See git archive.')
group.add_option('-g', '--groups',
dest='groups', default='default',
help='restrict manifest projects to ones with specified '
'group(s) [default|all|G1,G2,G3|G4,-G5,-G6]',
metavar='GROUP')
group.add_option('-p', '--platform',
dest='platform', default="auto",
help='restrict manifest projects to ones with a specified '
'platform group [auto|all|none|linux|darwin|...]',
metavar='PLATFORM')
# Tool
group = init_optparse.add_option_group('repo Version options')
@ -134,6 +206,11 @@ group.add_option('--no-repo-verify',
dest='no_repo_verify', action='store_true',
help='do not verify repo source code')
# Other
group = init_optparse.add_option_group('Other options')
group.add_option('--config-name',
dest='config_name', action="store_true", default=False,
help='Always prompt for name/e-mail')
class CloneFailure(Exception):
"""Indicate the remote clone of repo itself failed.
@ -144,7 +221,7 @@ def _Init(args):
"""Installs repo by cloning it over the network.
"""
opt, args = init_optparse.parse_args(args)
if args or not opt.manifest_url:
if args:
init_optparse.print_usage()
sys.exit(1)
@ -161,32 +238,27 @@ def _Init(args):
if branch.startswith('refs/heads/'):
branch = branch[len('refs/heads/'):]
if branch.startswith('refs/'):
print >>sys.stderr, "fatal: invalid branch name '%s'" % branch
_print("fatal: invalid branch name '%s'" % branch, file=sys.stderr)
raise CloneFailure()
if not os.path.isdir(repodir):
try:
os.mkdir(repodir)
except OSError, e:
print >>sys.stderr, \
'fatal: cannot make %s directory: %s' % (
repodir, e.strerror)
# Don't faise CloneFailure; that would delete the
try:
os.mkdir(repodir)
except OSError as e:
if e.errno != errno.EEXIST:
_print('fatal: cannot make %s directory: %s'
% (repodir, e.strerror), file=sys.stderr)
# Don't raise CloneFailure; that would delete the
# name. Instead exit immediately.
#
sys.exit(1)
_CheckGitVersion()
try:
if _NeedSetupGnuPG():
can_verify = _SetupGnuPG(opt.quiet)
if NeedSetupGnuPG():
can_verify = SetupGnuPG(opt.quiet)
else:
can_verify = True
if not opt.quiet:
print >>sys.stderr, 'Getting repo ...'
print >>sys.stderr, ' from %s' % url
dst = os.path.abspath(os.path.join(repodir, S_repo))
_Clone(url, dst, opt.quiet)
@ -198,31 +270,54 @@ def _Init(args):
_Checkout(dst, branch, rev, opt.quiet)
except CloneFailure:
if opt.quiet:
print >>sys.stderr, \
'fatal: repo init failed; run without --quiet to see why'
_print('fatal: repo init failed; run without --quiet to see why',
file=sys.stderr)
raise
def ParseGitVersion(ver_str):
if not ver_str.startswith('git version '):
return None
num_ver_str = ver_str[len('git version '):].strip().split('-')[0]
to_tuple = []
for num_str in num_ver_str.split('.')[:3]:
if num_str.isdigit():
to_tuple.append(int(num_str))
else:
to_tuple.append(0)
return tuple(to_tuple)
def _CheckGitVersion():
cmd = [GIT, '--version']
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE)
try:
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE)
except OSError as e:
_print(file=sys.stderr)
_print("fatal: '%s' is not available" % GIT, file=sys.stderr)
_print('fatal: %s' % e, file=sys.stderr)
_print(file=sys.stderr)
_print('Please make sure %s is installed and in your path.' % GIT,
file=sys.stderr)
raise CloneFailure()
ver_str = proc.stdout.read().strip()
proc.stdout.close()
proc.wait()
if not ver_str.startswith('git version '):
print >>sys.stderr, 'error: "%s" unsupported' % ver_str
ver_act = ParseGitVersion(ver_str)
if ver_act is None:
_print('error: "%s" unsupported' % ver_str, file=sys.stderr)
raise CloneFailure()
ver_str = ver_str[len('git version '):].strip()
ver_act = tuple(map(lambda x: int(x), ver_str.split('.')[0:3]))
if ver_act < MIN_GIT_VERSION:
need = '.'.join(map(lambda x: str(x), MIN_GIT_VERSION))
print >>sys.stderr, 'fatal: git %s or later required' % need
need = '.'.join(map(str, MIN_GIT_VERSION))
_print('fatal: git %s or later required' % need, file=sys.stderr)
raise CloneFailure()
def _NeedSetupGnuPG():
def NeedSetupGnuPG():
if not os.path.isdir(home_dot_repo):
return True
@ -234,29 +329,27 @@ def _NeedSetupGnuPG():
if not kv:
return True
kv = tuple(map(lambda x: int(x), kv.split('.')))
kv = tuple(map(int, kv.split('.')))
if kv < KEYRING_VERSION:
return True
return False
def _SetupGnuPG(quiet):
if not os.path.isdir(home_dot_repo):
try:
os.mkdir(home_dot_repo)
except OSError, e:
print >>sys.stderr, \
'fatal: cannot make %s directory: %s' % (
home_dot_repo, e.strerror)
def SetupGnuPG(quiet):
try:
os.mkdir(home_dot_repo)
except OSError as e:
if e.errno != errno.EEXIST:
_print('fatal: cannot make %s directory: %s'
% (home_dot_repo, e.strerror), file=sys.stderr)
sys.exit(1)
if not os.path.isdir(gpg_dir):
try:
os.mkdir(gpg_dir, 0700)
except OSError, e:
print >>sys.stderr, \
'fatal: cannot make %s directory: %s' % (
gpg_dir, e.strerror)
try:
os.mkdir(gpg_dir, stat.S_IRWXU)
except OSError as e:
if e.errno != errno.EEXIST:
_print('fatal: cannot make %s directory: %s' % (gpg_dir, e.strerror),
file=sys.stderr)
sys.exit(1)
env = os.environ.copy()
@ -267,23 +360,23 @@ def _SetupGnuPG(quiet):
proc = subprocess.Popen(cmd,
env = env,
stdin = subprocess.PIPE)
except OSError, e:
except OSError as e:
if not quiet:
print >>sys.stderr, 'warning: gpg (GnuPG) is not available.'
print >>sys.stderr, 'warning: Installing it is strongly encouraged.'
print >>sys.stderr
_print('warning: gpg (GnuPG) is not available.', file=sys.stderr)
_print('warning: Installing it is strongly encouraged.', file=sys.stderr)
_print(file=sys.stderr)
return False
proc.stdin.write(MAINTAINER_KEYS)
proc.stdin.close()
if proc.wait() != 0:
print >>sys.stderr, 'fatal: registering repo maintainer keys failed'
_print('fatal: registering repo maintainer keys failed', file=sys.stderr)
sys.exit(1)
print
_print()
fd = open(os.path.join(home_dot_repo, 'keyring-version'), 'w')
fd.write('.'.join(map(lambda x: str(x), KEYRING_VERSION)) + '\n')
fd.write('.'.join(map(str, KEYRING_VERSION)) + '\n')
fd.close()
return True
@ -296,15 +389,43 @@ def _SetConfig(local, name, value):
raise CloneFailure()
def _Fetch(local, quiet, *args):
def _InitHttp():
handlers = []
mgr = urllib.request.HTTPPasswordMgrWithDefaultRealm()
try:
import netrc
n = netrc.netrc()
for host in n.hosts:
p = n.hosts[host]
mgr.add_password(p[1], 'http://%s/' % host, p[0], p[2])
mgr.add_password(p[1], 'https://%s/' % host, p[0], p[2])
except:
pass
handlers.append(urllib.request.HTTPBasicAuthHandler(mgr))
handlers.append(urllib.request.HTTPDigestAuthHandler(mgr))
if 'http_proxy' in os.environ:
url = os.environ['http_proxy']
handlers.append(urllib.request.ProxyHandler({'http': url, 'https': url}))
if 'REPO_CURL_VERBOSE' in os.environ:
handlers.append(urllib.request.HTTPHandler(debuglevel=1))
handlers.append(urllib.request.HTTPSHandler(debuglevel=1))
urllib.request.install_opener(urllib.request.build_opener(*handlers))
def _Fetch(url, local, src, quiet):
if not quiet:
_print('Get %s' % url, file=sys.stderr)
cmd = [GIT, 'fetch']
if quiet:
cmd.append('--quiet')
err = subprocess.PIPE
else:
err = None
cmd.extend(args)
cmd.append('origin')
cmd.append(src)
cmd.append('+refs/heads/*:refs/remotes/origin/*')
cmd.append('refs/tags/*:refs/tags/*')
proc = subprocess.Popen(cmd, cwd = local, stderr = err)
if err:
@ -313,38 +434,96 @@ def _Fetch(local, quiet, *args):
if proc.wait() != 0:
raise CloneFailure()
def _DownloadBundle(url, local, quiet):
if not url.endswith('/'):
url += '/'
url += 'clone.bundle'
proc = subprocess.Popen(
[GIT, 'config', '--get-regexp', 'url.*.insteadof'],
cwd = local,
stdout = subprocess.PIPE)
for line in proc.stdout:
m = re.compile(r'^url\.(.*)\.insteadof (.*)$').match(line)
if m:
new_url = m.group(1)
old_url = m.group(2)
if url.startswith(old_url):
url = new_url + url[len(old_url):]
break
proc.stdout.close()
proc.wait()
if not url.startswith('http:') and not url.startswith('https:'):
return False
dest = open(os.path.join(local, '.git', 'clone.bundle'), 'w+b')
try:
try:
r = urllib.request.urlopen(url)
except urllib.error.HTTPError as e:
if e.code in [401, 403, 404]:
return False
_print('fatal: Cannot get %s' % url, file=sys.stderr)
_print('fatal: HTTP error %s' % e.code, file=sys.stderr)
raise CloneFailure()
except urllib.error.URLError as e:
_print('fatal: Cannot get %s' % url, file=sys.stderr)
_print('fatal: error %s' % e.reason, file=sys.stderr)
raise CloneFailure()
try:
if not quiet:
_print('Get %s' % url, file=sys.stderr)
while True:
buf = r.read(8192)
if buf == '':
return True
dest.write(buf)
finally:
r.close()
finally:
dest.close()
def _ImportBundle(local):
path = os.path.join(local, '.git', 'clone.bundle')
try:
_Fetch(local, local, path, True)
finally:
os.remove(path)
def _Clone(url, local, quiet):
"""Clones a git repository to a new subdirectory of repodir
"""
try:
os.mkdir(local)
except OSError, e:
print >>sys.stderr, \
'fatal: cannot make %s directory: %s' \
% (local, e.strerror)
except OSError as e:
_print('fatal: cannot make %s directory: %s' % (local, e.strerror),
file=sys.stderr)
raise CloneFailure()
cmd = [GIT, 'init', '--quiet']
try:
proc = subprocess.Popen(cmd, cwd = local)
except OSError, e:
print >>sys.stderr
print >>sys.stderr, "fatal: '%s' is not available" % GIT
print >>sys.stderr, 'fatal: %s' % e
print >>sys.stderr
print >>sys.stderr, 'Please make sure %s is installed'\
' and in your path.' % GIT
except OSError as e:
_print(file=sys.stderr)
_print("fatal: '%s' is not available" % GIT, file=sys.stderr)
_print('fatal: %s' % e, file=sys.stderr)
_print(file=sys.stderr)
_print('Please make sure %s is installed and in your path.' % GIT,
file=sys.stderr)
raise CloneFailure()
if proc.wait() != 0:
print >>sys.stderr, 'fatal: could not create %s' % local
_print('fatal: could not create %s' % local, file=sys.stderr)
raise CloneFailure()
_InitHttp()
_SetConfig(local, 'remote.origin.url', url)
_SetConfig(local, 'remote.origin.fetch',
'+refs/heads/*:refs/remotes/origin/*')
_Fetch(local, quiet)
_Fetch(local, quiet, '--tags')
if _DownloadBundle(url, local, quiet):
_ImportBundle(local)
else:
_Fetch(url, local, 'origin', quiet)
def _Verify(cwd, branch, quiet):
@ -362,21 +541,18 @@ def _Verify(cwd, branch, quiet):
proc.stderr.close()
if proc.wait() != 0 or not cur:
print >>sys.stderr
print >>sys.stderr,\
"fatal: branch '%s' has not been signed" \
% branch
_print(file=sys.stderr)
_print("fatal: branch '%s' has not been signed" % branch, file=sys.stderr)
raise CloneFailure()
m = re.compile(r'^(.*)-[0-9]{1,}-g[0-9a-f]{1,}$').match(cur)
if m:
cur = m.group(1)
if not quiet:
print >>sys.stderr
print >>sys.stderr, \
"info: Ignoring branch '%s'; using tagged release '%s'" \
% (branch, cur)
print >>sys.stderr
_print(file=sys.stderr)
_print("info: Ignoring branch '%s'; using tagged release '%s'"
% (branch, cur), file=sys.stderr)
_print(file=sys.stderr)
env = os.environ.copy()
env['GNUPGHOME'] = gpg_dir.encode()
@ -394,10 +570,10 @@ def _Verify(cwd, branch, quiet):
proc.stderr.close()
if proc.wait() != 0:
print >>sys.stderr
print >>sys.stderr, out
print >>sys.stderr, err
print >>sys.stderr
_print(file=sys.stderr)
_print(out, file=sys.stderr)
_print(err, file=sys.stderr)
_print(file=sys.stderr)
raise CloneFailure()
return '%s^0' % cur
@ -427,19 +603,19 @@ def _Checkout(cwd, branch, rev, quiet):
def _FindRepo():
"""Look for a repo installation, starting at the current directory.
"""
dir = os.getcwd()
curdir = os.getcwd()
repo = None
olddir = None
while dir != '/' \
and dir != olddir \
while curdir != '/' \
and curdir != olddir \
and not repo:
repo = os.path.join(dir, repodir, REPO_MAIN)
repo = os.path.join(curdir, repodir, REPO_MAIN)
if not os.path.isfile(repo):
repo = None
olddir = dir
dir = os.path.dirname(dir)
return (repo, os.path.join(dir, repodir))
olddir = curdir
curdir = os.path.dirname(curdir)
return (repo, os.path.join(curdir, repodir))
class _Options:
@ -451,7 +627,7 @@ def _ParseArguments(args):
opt = _Options()
arg = []
for i in xrange(0, len(args)):
for i in range(len(args)):
a = args[i]
if a == '-h' or a == '--help':
opt.help = True
@ -464,7 +640,7 @@ def _ParseArguments(args):
def _Usage():
print >>sys.stderr,\
_print(
"""usage: repo COMMAND [ARGS]
repo is not yet installed. Use "repo init" to install it here.
@ -475,7 +651,7 @@ The most commonly used repo commands are:
help Display detailed help on a command
For access to the full online help, install repo ("repo init").
"""
""", file=sys.stderr)
sys.exit(1)
@ -485,25 +661,23 @@ def _Help(args):
init_optparse.print_help()
sys.exit(0)
else:
print >>sys.stderr,\
"error: '%s' is not a bootstrap command.\n"\
' For access to online help, install repo ("repo init").'\
% args[0]
_print("error: '%s' is not a bootstrap command.\n"
' For access to online help, install repo ("repo init").'
% args[0], file=sys.stderr)
else:
_Usage()
sys.exit(1)
def _NotInstalled():
print >>sys.stderr,\
'error: repo is not installed. Use "repo init" to install it here.'
_print('error: repo is not installed. Use "repo init" to install it here.',
file=sys.stderr)
sys.exit(1)
def _NoCommands(cmd):
print >>sys.stderr,\
"""error: command '%s' requires repo to be installed first.
Use "repo init" to install it here.""" % cmd
_print("""error: command '%s' requires repo to be installed first.
Use "repo init" to install it here.""" % cmd, file=sys.stderr)
sys.exit(1)
@ -540,18 +714,18 @@ def _SetDefaultsTo(gitdir):
proc.stderr.close()
if proc.wait() != 0:
print >>sys.stderr, 'fatal: %s has no current branch' % gitdir
_print('fatal: %s has no current branch' % gitdir, file=sys.stderr)
sys.exit(1)
def main(orig_args):
main, dir = _FindRepo()
repo_main, rel_repo_dir = _FindRepo()
cmd, opt, args = _ParseArguments(orig_args)
wrapper_path = os.path.abspath(__file__)
my_main, my_git = _RunSelf(wrapper_path)
if not main:
if not repo_main:
if opt.help:
_Usage()
if cmd == 'help':
@ -564,36 +738,34 @@ def main(orig_args):
try:
_Init(args)
except CloneFailure:
for root, dirs, files in os.walk(repodir, topdown=False):
for name in files:
os.remove(os.path.join(root, name))
for name in dirs:
os.rmdir(os.path.join(root, name))
os.rmdir(repodir)
shutil.rmtree(os.path.join(repodir, S_repo), ignore_errors=True)
sys.exit(1)
main, dir = _FindRepo()
repo_main, rel_repo_dir = _FindRepo()
else:
_NoCommands(cmd)
if my_main:
main = my_main
repo_main = my_main
ver_str = '.'.join(map(lambda x: str(x), VERSION))
me = [main,
'--repo-dir=%s' % dir,
ver_str = '.'.join(map(str, VERSION))
me = [sys.executable, repo_main,
'--repo-dir=%s' % rel_repo_dir,
'--wrapper-version=%s' % ver_str,
'--wrapper-path=%s' % wrapper_path,
'--']
me.extend(orig_args)
me.extend(extra_args)
try:
os.execv(main, me)
except OSError, e:
print >>sys.stderr, "fatal: unable to start %s" % main
print >>sys.stderr, "fatal: %s" % e
os.execv(sys.executable, me)
except OSError as e:
_print("fatal: unable to start %s" % repo_main, file=sys.stderr)
_print("fatal: %s" % e, file=sys.stderr)
sys.exit(148)
if __name__ == '__main__':
if ver[0] == 3:
_print('warning: Python 3 support is currently experimental. YMMV.\n'
'Please use Python 2.6 - 2.7 instead.',
file=sys.stderr)
main(sys.argv[1:])

View File

@ -15,7 +15,7 @@
import os
all = {}
all_commands = {}
my_dir = os.path.dirname(__file__)
for py in os.listdir(my_dir):
@ -38,12 +38,12 @@ for py in os.listdir(my_dir):
try:
cmd = getattr(mod, clsn)()
except AttributeError:
raise SyntaxError, '%s/%s does not define class %s' % (
__name__, py, clsn)
raise SyntaxError('%s/%s does not define class %s' % (
__name__, py, clsn))
name = name.replace('_', '-')
cmd.NAME = name
all[name] = cmd
all_commands[name] = cmd
if 'help' in all:
all['help'].commands = all
if 'help' in all_commands:
all_commands['help'].commands = all_commands

View File

@ -13,6 +13,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import print_function
import sys
from command import Command
from git_command import git
@ -36,16 +37,16 @@ It is equivalent to "git branch -D <branchname>".
nb = args[0]
if not git.check_ref_format('heads/%s' % nb):
print >>sys.stderr, "error: '%s' is not a valid name" % nb
print("error: '%s' is not a valid name" % nb, file=sys.stderr)
sys.exit(1)
nb = args[0]
err = []
success = []
all = self.GetProjects(args[1:])
all_projects = self.GetProjects(args[1:])
pm = Progress('Abandon %s' % nb, len(all))
for project in all:
pm = Progress('Abandon %s' % nb, len(all_projects))
for project in all_projects:
pm.update()
status = project.AbandonBranch(nb)
@ -58,13 +59,13 @@ It is equivalent to "git branch -D <branchname>".
if err:
for p in err:
print >>sys.stderr,\
"error: %s/: cannot abandon %s" \
% (p.relpath, nb)
print("error: %s/: cannot abandon %s" % (p.relpath, nb),
file=sys.stderr)
sys.exit(1)
elif not success:
print >>sys.stderr, 'error: no project has branch %s' % nb
print('error: no project has branch %s' % nb, file=sys.stderr)
sys.exit(1)
else:
print >>sys.stderr, 'Abandoned in %d project(s):\n %s' % (
len(success), '\n '.join(p.relpath for p in success))
print('Abandoned in %d project(s):\n %s'
% (len(success), '\n '.join(p.relpath for p in success)),
file=sys.stderr)

View File

@ -13,6 +13,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import print_function
import sys
from color import Coloring
from command import Command
@ -45,6 +46,10 @@ class BranchInfo(object):
def IsCurrent(self):
return self.current > 0
@property
def IsSplitCurrent(self):
return self.current != 0 and self.current != len(self.projects)
@property
def IsPublished(self):
return self.published > 0
@ -93,21 +98,20 @@ is shown, then the branch appears in all projects.
def Execute(self, opt, args):
projects = self.GetProjects(args)
out = BranchColoring(self.manifest.manifestProject.config)
all = {}
all_branches = {}
project_cnt = len(projects)
for project in projects:
for name, b in project.GetBranches().iteritems():
for name, b in project.GetBranches().items():
b.project = project
if name not in all:
all[name] = BranchInfo(name)
all[name].add(b)
if name not in all_branches:
all_branches[name] = BranchInfo(name)
all_branches[name].add(b)
names = all.keys()
names.sort()
names = list(sorted(all_branches))
if not names:
print >>sys.stderr, ' (no branches)'
print(' (no branches)', file=sys.stderr)
return
width = 25
@ -116,7 +120,7 @@ is shown, then the branch appears in all projects.
width = len(name)
for name in names:
i = all[name]
i = all_branches[name]
in_cnt = len(i.projects)
if i.IsCurrent:
@ -139,13 +143,17 @@ is shown, then the branch appears in all projects.
if in_cnt < project_cnt:
fmt = out.write
paths = []
if in_cnt < project_cnt - in_cnt:
type = 'in'
non_cur_paths = []
if i.IsSplitCurrent or (in_cnt < project_cnt - in_cnt):
in_type = 'in'
for b in i.projects:
paths.append(b.project.relpath)
if not i.IsSplitCurrent or b.current:
paths.append(b.project.relpath)
else:
non_cur_paths.append(b.project.relpath)
else:
fmt = out.notinproject
type = 'not in'
in_type = 'not in'
have = set()
for b in i.projects:
have.add(b.project)
@ -153,14 +161,20 @@ is shown, then the branch appears in all projects.
if not p in have:
paths.append(p.relpath)
s = ' %s %s' % (type, ', '.join(paths))
if width + 7 + len(s) < 80:
s = ' %s %s' % (in_type, ', '.join(paths))
if not i.IsSplitCurrent and (width + 7 + len(s) < 80):
fmt = out.current if i.IsCurrent else fmt
fmt(s)
else:
fmt(' %s:' % type)
fmt(' %s:' % in_type)
fmt = out.current if i.IsCurrent else out.write
for p in paths:
out.nl()
fmt(width*' ' + ' %s' % p)
fmt = out.write
for p in non_cur_paths:
out.nl()
fmt(width*' ' + ' %s' % p)
else:
out.write(' in all projects')
out.nl()

View File

@ -13,6 +13,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import print_function
import sys
from command import Command
from progress import Progress
@ -39,10 +40,10 @@ The command is equivalent to:
nb = args[0]
err = []
success = []
all = self.GetProjects(args[1:])
all_projects = self.GetProjects(args[1:])
pm = Progress('Checkout %s' % nb, len(all))
for project in all:
pm = Progress('Checkout %s' % nb, len(all_projects))
for project in all_projects:
pm.update()
status = project.CheckoutBranch(nb)
@ -55,10 +56,9 @@ The command is equivalent to:
if err:
for p in err:
print >>sys.stderr,\
"error: %s/: cannot checkout %s" \
% (p.relpath, nb)
print("error: %s/: cannot checkout %s" % (p.relpath, nb),
file=sys.stderr)
sys.exit(1)
elif not success:
print >>sys.stderr, 'error: no project has branch %s' % nb
print('error: no project has branch %s' % nb, file=sys.stderr)
sys.exit(1)

View File

@ -13,7 +13,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import sys, re, string, random, os
from __future__ import print_function
import re
import sys
from command import Command
from git_command import GitCommand
@ -45,13 +47,13 @@ change id will be added.
capture_stdout = True,
capture_stderr = True)
if p.Wait() != 0:
print >>sys.stderr, p.stderr
print(p.stderr, file=sys.stderr)
sys.exit(1)
sha1 = p.stdout.strip()
p = GitCommand(None, ['cat-file', 'commit', sha1], capture_stdout=True)
if p.Wait() != 0:
print >>sys.stderr, "error: Failed to retrieve old commit message"
print("error: Failed to retrieve old commit message", file=sys.stderr)
sys.exit(1)
old_msg = self._StripHeader(p.stdout)
@ -61,8 +63,8 @@ change id will be added.
capture_stderr = True)
status = p.Wait()
print >>sys.stdout, p.stdout
print >>sys.stderr, p.stderr
print(p.stdout, file=sys.stdout)
print(p.stderr, file=sys.stderr)
if status == 0:
# The cherry-pick was applied correctly. We just need to edit the
@ -74,17 +76,16 @@ change id will be added.
capture_stdout = True,
capture_stderr = True)
p.stdin.write(new_msg)
p.stdin.close()
if p.Wait() != 0:
print >>sys.stderr, "error: Failed to update commit message"
print("error: Failed to update commit message", file=sys.stderr)
sys.exit(1)
else:
print >>sys.stderr, """\
NOTE: When committing (please see above) and editing the commit message,
please remove the old Change-Id-line and add:
"""
print >>sys.stderr, self._GetReference(sha1)
print >>sys.stderr
print('NOTE: When committing (please see above) and editing the commit '
'message, please remove the old Change-Id-line and add:')
print(self._GetReference(sha1), file=sys.stderr)
print(file=sys.stderr)
def _IsChangeId(self, line):
return CHANGE_ID_RE.match(line)

View File

@ -20,8 +20,21 @@ class Diff(PagedCommand):
helpSummary = "Show changes between commit and working tree"
helpUsage = """
%prog [<project>...]
The -u option causes '%prog' to generate diff output with file paths
relative to the repository root, so the output can be applied
to the Unix 'patch' command.
"""
def _Options(self, p):
def cmd(option, opt_str, value, parser):
setattr(parser.values, option.dest, list(parser.rargs))
while parser.rargs:
del parser.rargs[0]
p.add_option('-u', '--absolute',
dest='absolute', action='store_true',
help='Paths are relative to the repository root')
def Execute(self, opt, args):
for project in self.GetProjects(args):
project.PrintWorkTreeDiff()
project.PrintWorkTreeDiff(opt.absolute)

195
subcmds/diffmanifests.py Normal file
View File

@ -0,0 +1,195 @@
#
# Copyright (C) 2014 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from color import Coloring
from command import PagedCommand
from manifest_xml import XmlManifest
class _Coloring(Coloring):
def __init__(self, config):
Coloring.__init__(self, config, "status")
class Diffmanifests(PagedCommand):
""" A command to see logs in projects represented by manifests
This is used to see deeper differences between manifests. Where a simple
diff would only show a diff of sha1s for example, this command will display
the logs of the project between both sha1s, allowing user to see diff at a
deeper level.
"""
common = True
helpSummary = "Manifest diff utility"
helpUsage = """%prog manifest1.xml [manifest2.xml] [options]"""
helpDescription = """
The %prog command shows differences between project revisions of manifest1 and
manifest2. if manifest2 is not specified, current manifest.xml will be used
instead. Both absolute and relative paths may be used for manifests. Relative
paths start from project's ".repo/manifests" folder.
The --raw option Displays the diff in a way that facilitates parsing, the
project pattern will be <status> <path> <revision from> [<revision to>] and the
commit pattern will be <status> <onelined log> with status values respectively :
A = Added project
R = Removed project
C = Changed project
U = Project with unreachable revision(s) (revision(s) not found)
for project, and
A = Added commit
R = Removed commit
for a commit.
Only changed projects may contain commits, and commit status always starts with
a space, and are part of last printed project.
Unreachable revisions may occur if project is not up to date or if repo has not
been initialized with all the groups, in which case some projects won't be
synced and their revisions won't be found.
"""
def _Options(self, p):
p.add_option('--raw',
dest='raw', action='store_true',
help='Display raw diff.')
p.add_option('--no-color',
dest='color', action='store_false', default=True,
help='does not display the diff in color.')
def _printRawDiff(self, diff):
for project in diff['added']:
self.printText("A %s %s" % (project.relpath, project.revisionExpr))
self.out.nl()
for project in diff['removed']:
self.printText("R %s %s" % (project.relpath, project.revisionExpr))
self.out.nl()
for project, otherProject in diff['changed']:
self.printText("C %s %s %s" % (project.relpath, project.revisionExpr,
otherProject.revisionExpr))
self.out.nl()
self._printLogs(project, otherProject, raw=True, color=False)
for project, otherProject in diff['unreachable']:
self.printText("U %s %s %s" % (project.relpath, project.revisionExpr,
otherProject.revisionExpr))
self.out.nl()
def _printDiff(self, diff, color=True):
if diff['added']:
self.out.nl()
self.printText('added projects : \n')
self.out.nl()
for project in diff['added']:
self.printProject('\t%s' % (project.relpath))
self.printText(' at revision ')
self.printRevision(project.revisionExpr)
self.out.nl()
if diff['removed']:
self.out.nl()
self.printText('removed projects : \n')
self.out.nl()
for project in diff['removed']:
self.printProject('\t%s' % (project.relpath))
self.printText(' at revision ')
self.printRevision(project.revisionExpr)
self.out.nl()
if diff['changed']:
self.out.nl()
self.printText('changed projects : \n')
self.out.nl()
for project, otherProject in diff['changed']:
self.printProject('\t%s' % (project.relpath))
self.printText(' changed from ')
self.printRevision(project.revisionExpr)
self.printText(' to ')
self.printRevision(otherProject.revisionExpr)
self.out.nl()
self._printLogs(project, otherProject, raw=False, color=color)
self.out.nl()
if diff['unreachable']:
self.out.nl()
self.printText('projects with unreachable revisions : \n')
self.out.nl()
for project, otherProject in diff['unreachable']:
self.printProject('\t%s ' % (project.relpath))
self.printRevision(project.revisionExpr)
self.printText(' or ')
self.printRevision(otherProject.revisionExpr)
self.printText(' not found')
self.out.nl()
def _printLogs(self, project, otherProject, raw=False, color=True):
logs = project.getAddedAndRemovedLogs(otherProject, oneline=True,
color=color)
if logs['removed']:
removedLogs = logs['removed'].split('\n')
for log in removedLogs:
if log.strip():
if raw:
self.printText(' R ' + log)
self.out.nl()
else:
self.printRemoved('\t\t[-] ')
self.printText(log)
self.out.nl()
if logs['added']:
addedLogs = logs['added'].split('\n')
for log in addedLogs:
if log.strip():
if raw:
self.printText(' A ' + log)
self.out.nl()
else:
self.printAdded('\t\t[+] ')
self.printText(log)
self.out.nl()
def Execute(self, opt, args):
if not args or len(args) > 2:
self.Usage()
self.out = _Coloring(self.manifest.globalConfig)
self.printText = self.out.nofmt_printer('text')
if opt.color:
self.printProject = self.out.nofmt_printer('project', attr = 'bold')
self.printAdded = self.out.nofmt_printer('green', fg = 'green', attr = 'bold')
self.printRemoved = self.out.nofmt_printer('red', fg = 'red', attr = 'bold')
self.printRevision = self.out.nofmt_printer('revision', fg = 'yellow')
else:
self.printProject = self.printAdded = self.printRemoved = self.printRevision = self.printText
manifest1 = XmlManifest(self.manifest.repodir)
manifest1.Override(args[0])
if len(args) == 1:
manifest2 = self.manifest
else:
manifest2 = XmlManifest(self.manifest.repodir)
manifest2.Override(args[1])
diff = manifest1.projectsDiff(manifest2)
if opt.raw:
self._printRawDiff(diff)
else:
self._printDiff(diff, color=opt.color)

View File

@ -13,11 +13,12 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import os
from __future__ import print_function
import re
import sys
from command import Command
from error import GitError
CHANGE_RE = re.compile(r'^([1-9][0-9]*)(?:[/\.-]([1-9][0-9]*))?$')
@ -33,7 +34,15 @@ makes it available in your project's local working directory.
"""
def _Options(self, p):
pass
p.add_option('-c', '--cherry-pick',
dest='cherrypick', action='store_true',
help="cherry-pick instead of checkout")
p.add_option('-r', '--revert',
dest='revert', action='store_true',
help="revert instead of checkout")
p.add_option('-f', '--ff-only',
dest='ffonly', action='store_true',
help="force fast-forward merge")
def _ParseChangeIds(self, args):
if not args:
@ -61,21 +70,34 @@ makes it available in your project's local working directory.
for project, change_id, ps_id in self._ParseChangeIds(args):
dl = project.DownloadPatchSet(change_id, ps_id)
if not dl:
print >>sys.stderr, \
'[%s] change %d/%d not found' \
% (project.name, change_id, ps_id)
print('[%s] change %d/%d not found'
% (project.name, change_id, ps_id),
file=sys.stderr)
sys.exit(1)
if not dl.commits:
print >>sys.stderr, \
'[%s] change %d/%d has already been merged' \
% (project.name, change_id, ps_id)
if not opt.revert and not dl.commits:
print('[%s] change %d/%d has already been merged'
% (project.name, change_id, ps_id),
file=sys.stderr)
continue
if len(dl.commits) > 1:
print >>sys.stderr, \
'[%s] %d/%d depends on %d unmerged changes:' \
% (project.name, change_id, ps_id, len(dl.commits))
print('[%s] %d/%d depends on %d unmerged changes:' \
% (project.name, change_id, ps_id, len(dl.commits)),
file=sys.stderr)
for c in dl.commits:
print >>sys.stderr, ' %s' % (c)
project._Checkout(dl.commit)
print(' %s' % (c), file=sys.stderr)
if opt.cherrypick:
try:
project._CherryPick(dl.commit)
except GitError:
print('[%s] Could not complete the cherry-pick of %s' \
% (project.name, dl.commit), file=sys.stderr)
sys.exit(1)
elif opt.revert:
project._Revert(dl.commit)
elif opt.ffonly:
project._FastForward(dl.commit, ffonly=True)
else:
project._Checkout(dl.commit)

View File

@ -13,10 +13,14 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import print_function
import errno
import fcntl
import multiprocessing
import re
import os
import select
import signal
import sys
import subprocess
@ -30,6 +34,7 @@ _CAN_COLOR = [
'log',
]
class ForallColoring(Coloring):
def __init__(self, config):
Coloring.__init__(self, config, 'forall')
@ -41,10 +46,14 @@ class Forall(Command, MirrorSafeCommand):
helpSummary = "Run a shell command in each project"
helpUsage = """
%prog [<project>...] -c <command> [<arg>...]
%prog -r str1 [str2] ... -c <command> [<arg>...]"
"""
helpDescription = """
Executes the same shell command in each project.
The -r option allows running the command only on projects matching
regex or wildcard expression.
Output Formatting
-----------------
@ -82,11 +91,25 @@ revision to a locally executed git command, use REPO_LREV.
REPO_RREV is the name of the revision from the manifest, exactly
as written in the manifest.
REPO_COUNT is the total number of projects being iterated.
REPO_I is the current (1-based) iteration count. Can be used in
conjunction with REPO_COUNT to add a simple progress indicator to your
command.
REPO__* are any extra environment variables, specified by the
"annotation" element under any project element. This can be useful
for differentiating trees based on user-specific criteria, or simply
annotating tree details.
shell positional arguments ($1, $2, .., $#) are set to any arguments
following <command>.
Unless -p is used, stdin, stdout, stderr are inherited from the
terminal and are not redirected.
If -e is used, when a command exits unsuccessfully, '%prog' will abort
without iterating through the remaining projects.
"""
def _Options(self, p):
@ -94,11 +117,17 @@ terminal and are not redirected.
setattr(parser.values, option.dest, list(parser.rargs))
while parser.rargs:
del parser.rargs[0]
p.add_option('-r', '--regex',
dest='regex', action='store_true',
help="Execute the command only on projects matching regex or wildcard expression")
p.add_option('-c', '--command',
help='Command (and arguments) to execute',
dest='command',
action='callback',
callback=cmd)
p.add_option('-e', '--abort-on-errors',
dest='abort_on_errors', action='store_true',
help='Abort if a command exits unsuccessfully')
g = p.add_option_group('Output')
g.add_option('-p',
@ -107,9 +136,35 @@ terminal and are not redirected.
g.add_option('-v', '--verbose',
dest='verbose', action='store_true',
help='Show command error messages')
g.add_option('-j', '--jobs',
dest='jobs', action='store', type='int', default=1,
help='number of commands to execute simultaneously')
def WantPager(self, opt):
return opt.project_header
return opt.project_header and opt.jobs == 1
def _SerializeProject(self, project):
""" Serialize a project._GitGetByExec instance.
project._GitGetByExec is not pickle-able. Instead of trying to pass it
around between processes, make a dict ourselves containing only the
attributes that we need.
"""
if not self.manifest.IsMirror:
lrev = project.GetRevisionId()
else:
lrev = None
return {
'name': project.name,
'relpath': project.relpath,
'remote_name': project.remote.name,
'lrev': lrev,
'rrev': project.revisionExpr,
'annotations': dict((a.name, a.value) for a in project.annotations),
'gitdir': project.gitdir,
'worktree': project.worktree,
}
def Execute(self, opt, args):
if not opt.command:
@ -136,117 +191,200 @@ terminal and are not redirected.
for cn in cmd[1:]:
if not cn.startswith('-'):
break
if cn in _CAN_COLOR:
else:
cn = None
# pylint: disable=W0631
if cn and cn in _CAN_COLOR:
class ColorCmd(Coloring):
def __init__(self, config, cmd):
Coloring.__init__(self, config, cmd)
if ColorCmd(self.manifest.manifestProject.config, cn).is_on:
cmd.insert(cmd.index(cn) + 1, '--color')
# pylint: enable=W0631
mirror = self.manifest.IsMirror
out = ForallColoring(self.manifest.manifestProject.config)
out.redirect(sys.stdout)
rc = 0
first = True
for project in self.GetProjects(args):
env = os.environ.copy()
def setenv(name, val):
if val is None:
val = ''
env[name] = val.encode()
smart_sync_manifest_name = "smart_sync_override.xml"
smart_sync_manifest_path = os.path.join(
self.manifest.manifestProject.worktree, smart_sync_manifest_name)
setenv('REPO_PROJECT', project.name)
setenv('REPO_PATH', project.relpath)
setenv('REPO_REMOTE', project.remote.name)
setenv('REPO_LREV', project.GetRevisionId())
setenv('REPO_RREV', project.revisionExpr)
if os.path.isfile(smart_sync_manifest_path):
self.manifest.Override(smart_sync_manifest_path)
if mirror:
setenv('GIT_DIR', project.gitdir)
cwd = project.gitdir
else:
cwd = project.worktree
if not opt.regex:
projects = self.GetProjects(args)
else:
projects = self.FindProjects(args)
if not os.path.exists(cwd):
if (opt.project_header and opt.verbose) \
or not opt.project_header:
print >>sys.stderr, 'skipping %s/' % project.relpath
continue
os.environ['REPO_COUNT'] = str(len(projects))
if opt.project_header:
stdin = subprocess.PIPE
stdout = subprocess.PIPE
stderr = subprocess.PIPE
else:
stdin = None
stdout = None
stderr = None
p = subprocess.Popen(cmd,
cwd = cwd,
shell = shell,
env = env,
stdin = stdin,
stdout = stdout,
stderr = stderr)
if opt.project_header:
class sfd(object):
def __init__(self, fd, dest):
self.fd = fd
self.dest = dest
def fileno(self):
return self.fd.fileno()
empty = True
didout = False
errbuf = ''
p.stdin.close()
s_in = [sfd(p.stdout, sys.stdout),
sfd(p.stderr, sys.stderr)]
for s in s_in:
flags = fcntl.fcntl(s.fd, fcntl.F_GETFL)
fcntl.fcntl(s.fd, fcntl.F_SETFL, flags | os.O_NONBLOCK)
while s_in:
in_ready, out_ready, err_ready = select.select(s_in, [], [])
for s in in_ready:
buf = s.fd.read(4096)
if not buf:
s.fd.close()
s_in.remove(s)
continue
if not opt.verbose:
if s.fd == p.stdout:
didout = True
else:
errbuf += buf
continue
if empty:
if first:
first = False
else:
out.nl()
out.project('project %s/', project.relpath)
out.nl()
out.flush()
if errbuf:
sys.stderr.write(errbuf)
sys.stderr.flush()
errbuf = ''
empty = False
s.dest.write(buf)
s.dest.flush()
r = p.wait()
if r != 0 and r != rc:
rc = r
pool = multiprocessing.Pool(opt.jobs, InitWorker)
try:
config = self.manifest.manifestProject.config
results_it = pool.imap(
DoWorkWrapper,
self.ProjectArgs(projects, mirror, opt, cmd, shell, config))
pool.close()
for r in results_it:
rc = rc or r
if r != 0 and opt.abort_on_errors:
raise Exception('Aborting due to previous error')
except (KeyboardInterrupt, WorkerKeyboardInterrupt):
# Catch KeyboardInterrupt raised inside and outside of workers
print('Interrupted - terminating the pool')
pool.terminate()
rc = rc or errno.EINTR
except Exception as e:
# Catch any other exceptions raised
print('Got an error, terminating the pool: %r' % e,
file=sys.stderr)
pool.terminate()
rc = rc or getattr(e, 'errno', 1)
finally:
pool.join()
if rc != 0:
sys.exit(rc)
def ProjectArgs(self, projects, mirror, opt, cmd, shell, config):
for cnt, p in enumerate(projects):
try:
project = self._SerializeProject(p)
except Exception as e:
print('Project list error: %r' % e,
file=sys.stderr)
return
except KeyboardInterrupt:
print('Project list interrupted',
file=sys.stderr)
return
yield [mirror, opt, cmd, shell, cnt, config, project]
class WorkerKeyboardInterrupt(Exception):
""" Keyboard interrupt exception for worker processes. """
pass
def InitWorker():
signal.signal(signal.SIGINT, signal.SIG_IGN)
def DoWorkWrapper(args):
""" A wrapper around the DoWork() method.
Catch the KeyboardInterrupt exceptions here and re-raise them as a different,
``Exception``-based exception to stop it flooding the console with stacktraces
and making the parent hang indefinitely.
"""
project = args.pop()
try:
return DoWork(project, *args)
except KeyboardInterrupt:
print('%s: Worker interrupted' % project['name'])
raise WorkerKeyboardInterrupt()
def DoWork(project, mirror, opt, cmd, shell, cnt, config):
env = os.environ.copy()
def setenv(name, val):
if val is None:
val = ''
if hasattr(val, 'encode'):
val = val.encode()
env[name] = val
setenv('REPO_PROJECT', project['name'])
setenv('REPO_PATH', project['relpath'])
setenv('REPO_REMOTE', project['remote_name'])
setenv('REPO_LREV', project['lrev'])
setenv('REPO_RREV', project['rrev'])
setenv('REPO_I', str(cnt + 1))
for name in project['annotations']:
setenv("REPO__%s" % (name), project['annotations'][name])
if mirror:
setenv('GIT_DIR', project['gitdir'])
cwd = project['gitdir']
else:
cwd = project['worktree']
if not os.path.exists(cwd):
if (opt.project_header and opt.verbose) \
or not opt.project_header:
print('skipping %s/' % project['relpath'], file=sys.stderr)
return
if opt.project_header:
stdin = subprocess.PIPE
stdout = subprocess.PIPE
stderr = subprocess.PIPE
else:
stdin = None
stdout = None
stderr = None
p = subprocess.Popen(cmd,
cwd=cwd,
shell=shell,
env=env,
stdin=stdin,
stdout=stdout,
stderr=stderr)
if opt.project_header:
out = ForallColoring(config)
out.redirect(sys.stdout)
class sfd(object):
def __init__(self, fd, dest):
self.fd = fd
self.dest = dest
def fileno(self):
return self.fd.fileno()
empty = True
errbuf = ''
p.stdin.close()
s_in = [sfd(p.stdout, sys.stdout),
sfd(p.stderr, sys.stderr)]
for s in s_in:
flags = fcntl.fcntl(s.fd, fcntl.F_GETFL)
fcntl.fcntl(s.fd, fcntl.F_SETFL, flags | os.O_NONBLOCK)
while s_in:
in_ready, _out_ready, _err_ready = select.select(s_in, [], [])
for s in in_ready:
buf = s.fd.read(4096)
if not buf:
s.fd.close()
s_in.remove(s)
continue
if not opt.verbose:
if s.fd != p.stdout:
errbuf += buf
continue
if empty and out:
if not cnt == 0:
out.nl()
if mirror:
project_header_path = project['name']
else:
project_header_path = project['relpath']
out.project('project %s/', project_header_path)
out.nl()
out.flush()
if errbuf:
sys.stderr.write(errbuf)
sys.stderr.flush()
errbuf = ''
empty = False
s.dest.write(buf)
s.dest.flush()
r = p.wait()
return r

View File

@ -13,8 +13,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import print_function
import sys
from optparse import SUPPRESS_HELP
from color import Coloring
from command import PagedCommand
from git_command import git_require, GitCommand
@ -52,7 +52,7 @@ Examples
Look for a line that has '#define' and either 'MAX_PATH or 'PATH_MAX':
repo grep -e '#define' --and -\( -e MAX_PATH -e PATH_MAX \)
repo grep -e '#define' --and -\\( -e MAX_PATH -e PATH_MAX \\)
Look for a line that has 'NODE' or 'Unexpected' in files that
contain a line that matches both expressions:
@ -85,7 +85,7 @@ contain a line that matches both expressions:
g.add_option('--cached',
action='callback', callback=carry,
help='Search the index, instead of the work tree')
g.add_option('-r','--revision',
g.add_option('-r', '--revision',
dest='revision', action='append', metavar='TREEish',
help='Search TREEish, instead of the work tree')
@ -97,7 +97,7 @@ contain a line that matches both expressions:
g.add_option('-i', '--ignore-case',
action='callback', callback=carry,
help='Ignore case differences')
g.add_option('-a','--text',
g.add_option('-a', '--text',
action='callback', callback=carry,
help="Process binary files as if they were text")
g.add_option('-I',
@ -126,7 +126,7 @@ contain a line that matches both expressions:
g.add_option('--and', '--or', '--not',
action='callback', callback=carry,
help='Boolean operators to combine patterns')
g.add_option('-(','-)',
g.add_option('-(', '-)',
action='callback', callback=carry,
help='Boolean operator grouping')
@ -146,10 +146,10 @@ contain a line that matches both expressions:
action='callback', callback=carry,
metavar='CONTEXT', type='str',
help='Show CONTEXT lines after match')
g.add_option('-l','--name-only','--files-with-matches',
g.add_option('-l', '--name-only', '--files-with-matches',
action='callback', callback=carry,
help='Show only file names containing matching lines')
g.add_option('-L','--files-without-match',
g.add_option('-L', '--files-without-match',
action='callback', callback=carry,
help='Show only file names not containing matching lines')
@ -158,9 +158,9 @@ contain a line that matches both expressions:
out = GrepColoring(self.manifest.manifestProject.config)
cmd_argv = ['grep']
if out.is_on and git_require((1,6,3)):
if out.is_on and git_require((1, 6, 3)):
cmd_argv.append('--color')
cmd_argv.extend(getattr(opt,'cmd_argv',[]))
cmd_argv.extend(getattr(opt, 'cmd_argv', []))
if '-e' not in cmd_argv:
if not args:
@ -179,8 +179,7 @@ contain a line that matches both expressions:
have_rev = False
if opt.revision:
if '--cached' in cmd_argv:
print >>sys.stderr,\
'fatal: cannot combine --cached and --revision'
print('fatal: cannot combine --cached and --revision', file=sys.stderr)
sys.exit(1)
have_rev = True
cmd_argv.extend(opt.revision)
@ -231,13 +230,13 @@ contain a line that matches both expressions:
out.nl()
else:
for line in r:
print line
print(line)
if have_match:
sys.exit(0)
elif have_rev and bad_rev:
for r in opt.revision:
print >>sys.stderr, "error: can't search revision %s" % r
print("error: can't search revision %s" % r, file=sys.stderr)
sys.exit(1)
else:
sys.exit(1)

View File

@ -13,6 +13,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import print_function
import re
import sys
from formatter import AbstractFormatter, DumbWriter
@ -31,12 +32,9 @@ Displays detailed usage information about a command.
"""
def _PrintAllCommands(self):
print 'usage: repo COMMAND [ARGS]'
print """
The complete list of recognized repo commands are:
"""
commandNames = self.commands.keys()
commandNames.sort()
print('usage: repo COMMAND [ARGS]')
print('The complete list of recognized repo commands are:')
commandNames = list(sorted(self.commands))
maxlen = 0
for name in commandNames:
@ -49,20 +47,16 @@ The complete list of recognized repo commands are:
summary = command.helpSummary.strip()
except AttributeError:
summary = ''
print fmt % (name, summary)
print """
See 'repo help <command>' for more information on a specific command.
"""
print(fmt % (name, summary))
print("See 'repo help <command>' for more information on a "
'specific command.')
def _PrintCommonCommands(self):
print 'usage: repo COMMAND [ARGS]'
print """
The most commonly used repo commands are:
"""
commandNames = [name
for name in self.commands.keys()
if self.commands[name].common]
commandNames.sort()
print('usage: repo COMMAND [ARGS]')
print('The most commonly used repo commands are:')
commandNames = list(sorted([name
for name, command in self.commands.items()
if command.common]))
maxlen = 0
for name in commandNames:
@ -75,11 +69,10 @@ The most commonly used repo commands are:
summary = command.helpSummary.strip()
except AttributeError:
summary = ''
print fmt % (name, summary)
print """
See 'repo help <command>' for more information on a specific command.
See 'repo help --all' for a complete list of recognized commands.
"""
print(fmt % (name, summary))
print(
"See 'repo help <command>' for more information on a specific command.\n"
"See 'repo help --all' for a complete list of recognized commands.")
def _PrintCommandHelp(self, cmd):
class _Out(Coloring):
@ -120,8 +113,8 @@ See 'repo help --all' for a complete list of recognized commands.
m = asciidoc_hdr.match(para)
if m:
title = m.group(1)
type = m.group(2)
if type[0] in ('=', '-'):
section_type = m.group(2)
if section_type[0] in ('=', '-'):
p = self.heading
else:
def _p(fmt, *args):
@ -131,7 +124,7 @@ See 'repo help --all' for a complete list of recognized commands.
p('%s', title)
self.nl()
p('%s', ''.ljust(len(title),type[0]))
p('%s', ''.ljust(len(title), section_type[0]))
self.nl()
continue
@ -162,9 +155,10 @@ See 'repo help --all' for a complete list of recognized commands.
try:
cmd = self.commands[name]
except KeyError:
print >>sys.stderr, "repo: '%s' is not a repo command." % name
print("repo: '%s' is not a repo command." % name, file=sys.stderr)
sys.exit(1)
cmd.manifest = self.manifest
self._PrintCommandHelp(cmd)
else:

201
subcmds/info.py Normal file
View File

@ -0,0 +1,201 @@
#
# Copyright (C) 2012 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from command import PagedCommand
from color import Coloring
from error import NoSuchProjectError
from git_refs import R_M
class _Coloring(Coloring):
def __init__(self, config):
Coloring.__init__(self, config, "status")
class Info(PagedCommand):
common = True
helpSummary = "Get info on the manifest branch, current branch or unmerged branches"
helpUsage = "%prog [-dl] [-o [-b]] [<project>...]"
def _Options(self, p):
p.add_option('-d', '--diff',
dest='all', action='store_true',
help="show full info and commit diff including remote branches")
p.add_option('-o', '--overview',
dest='overview', action='store_true',
help='show overview of all local commits')
p.add_option('-b', '--current-branch',
dest="current_branch", action="store_true",
help="consider only checked out branches")
p.add_option('-l', '--local-only',
dest="local", action="store_true",
help="Disable all remote operations")
def Execute(self, opt, args):
self.out = _Coloring(self.manifest.globalConfig)
self.heading = self.out.printer('heading', attr = 'bold')
self.headtext = self.out.printer('headtext', fg = 'yellow')
self.redtext = self.out.printer('redtext', fg = 'red')
self.sha = self.out.printer("sha", fg = 'yellow')
self.text = self.out.nofmt_printer('text')
self.dimtext = self.out.printer('dimtext', attr = 'dim')
self.opt = opt
manifestConfig = self.manifest.manifestProject.config
mergeBranch = manifestConfig.GetBranch("default").merge
manifestGroups = (manifestConfig.GetString('manifest.groups')
or 'all,-notdefault')
self.heading("Manifest branch: ")
self.headtext(self.manifest.default.revisionExpr)
self.out.nl()
self.heading("Manifest merge branch: ")
self.headtext(mergeBranch)
self.out.nl()
self.heading("Manifest groups: ")
self.headtext(manifestGroups)
self.out.nl()
self.printSeparator()
if not opt.overview:
self.printDiffInfo(args)
else:
self.printCommitOverview(args)
def printSeparator(self):
self.text("----------------------------")
self.out.nl()
def printDiffInfo(self, args):
try:
projs = self.GetProjects(args)
except NoSuchProjectError:
return
for p in projs:
self.heading("Project: ")
self.headtext(p.name)
self.out.nl()
self.heading("Mount path: ")
self.headtext(p.worktree)
self.out.nl()
self.heading("Current revision: ")
self.headtext(p.revisionExpr)
self.out.nl()
localBranches = p.GetBranches().keys()
self.heading("Local Branches: ")
self.redtext(str(len(localBranches)))
if len(localBranches) > 0:
self.text(" [")
self.text(", ".join(localBranches))
self.text("]")
self.out.nl()
if self.opt.all:
self.findRemoteLocalDiff(p)
self.printSeparator()
def findRemoteLocalDiff(self, project):
#Fetch all the latest commits
if not self.opt.local:
project.Sync_NetworkHalf(quiet=True, current_branch_only=True)
logTarget = R_M + self.manifest.manifestProject.config.GetBranch("default").merge
bareTmp = project.bare_git._bare
project.bare_git._bare = False
localCommits = project.bare_git.rev_list(
'--abbrev=8',
'--abbrev-commit',
'--pretty=oneline',
logTarget + "..",
'--')
originCommits = project.bare_git.rev_list(
'--abbrev=8',
'--abbrev-commit',
'--pretty=oneline',
".." + logTarget,
'--')
project.bare_git._bare = bareTmp
self.heading("Local Commits: ")
self.redtext(str(len(localCommits)))
self.dimtext(" (on current branch)")
self.out.nl()
for c in localCommits:
split = c.split()
self.sha(split[0] + " ")
self.text(" ".join(split[1:]))
self.out.nl()
self.printSeparator()
self.heading("Remote Commits: ")
self.redtext(str(len(originCommits)))
self.out.nl()
for c in originCommits:
split = c.split()
self.sha(split[0] + " ")
self.text(" ".join(split[1:]))
self.out.nl()
def printCommitOverview(self, args):
all_branches = []
for project in self.GetProjects(args):
br = [project.GetUploadableBranch(x)
for x in project.GetBranches()]
br = [x for x in br if x]
if self.opt.current_branch:
br = [x for x in br if x.name == project.CurrentBranch]
all_branches.extend(br)
if not all_branches:
return
self.out.nl()
self.heading('Projects Overview')
project = None
for branch in all_branches:
if project != branch.project:
project = branch.project
self.out.nl()
self.headtext(project.relpath)
self.out.nl()
commits = branch.commits
date = branch.date
self.text('%s %-33s (%2d commit%s, %s)' % (
branch.name == project.CurrentBranch and '*' or ' ',
branch.name,
len(commits),
len(commits) != 1 and 's' or '',
date))
self.out.nl()
for commit in commits:
split = commit.split()
self.text('{0:38}{1} '.format('','-'))
self.sha(split[0] + " ")
self.text(" ".join(split[1:]))
self.out.nl()

View File

@ -13,14 +13,27 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import print_function
import os
import platform
import re
import shutil
import sys
from pyversion import is_python3
if is_python3():
import urllib.parse
else:
import imp
import urlparse
urllib = imp.new_module('urllib')
urllib.parse = urlparse
from color import Coloring
from command import InteractiveCommand, MirrorSafeCommand
from error import ManifestParseError
from project import SyncBuffer
from git_config import GitConfig
from git_command import git_require, MIN_GIT_VERSION
class Init(InteractiveCommand, MirrorSafeCommand):
@ -78,10 +91,28 @@ to update the working directory files.
help='initial manifest file', metavar='NAME.xml')
g.add_option('--mirror',
dest='mirror', action='store_true',
help='mirror the forrest')
help='create a replica of the remote repositories '
'rather than a client working directory')
g.add_option('--reference',
dest='reference',
help='location of mirror directory', metavar='DIR')
g.add_option('--depth', type='int', default=None,
dest='depth',
help='create a shallow clone with given depth; see git clone')
g.add_option('--archive',
dest='archive', action='store_true',
help='checkout an archive instead of a git repository for '
'each project. See git archive.')
g.add_option('-g', '--groups',
dest='groups', default='default',
help='restrict manifest projects to ones with specified '
'group(s) [default|all|G1,G2,G3|G4,-G5,-G6]',
metavar='GROUP')
g.add_option('-p', '--platform',
dest='platform', default='auto',
help='restrict manifest projects to ones with a specified '
'platform group [auto|all|none|linux|darwin|...]',
metavar='PLATFORM')
# Tool
g = p.add_option_group('repo Version options')
@ -95,19 +126,41 @@ to update the working directory files.
dest='no_repo_verify', action='store_true',
help='do not verify repo source code')
# Other
g = p.add_option_group('Other options')
g.add_option('--config-name',
dest='config_name', action="store_true", default=False,
help='Always prompt for name/e-mail')
def _RegisteredEnvironmentOptions(self):
return {'REPO_MANIFEST_URL': 'manifest_url',
'REPO_MIRROR_LOCATION': 'reference'}
def _SyncManifest(self, opt):
m = self.manifest.manifestProject
is_new = not m.Exists
if is_new:
if not opt.manifest_url:
print >>sys.stderr, 'fatal: manifest url (-u) is required.'
print('fatal: manifest url (-u) is required.', file=sys.stderr)
sys.exit(1)
if not opt.quiet:
print >>sys.stderr, 'Getting manifest ...'
print >>sys.stderr, ' from %s' % opt.manifest_url
m._InitGitDir()
print('Get %s' % GitConfig.ForUser().UrlInsteadOf(opt.manifest_url),
file=sys.stderr)
# The manifest project object doesn't keep track of the path on the
# server where this git is located, so let's save that here.
mirrored_manifest_git = None
if opt.reference:
manifest_git_path = urllib.parse.urlparse(opt.manifest_url).path[1:]
mirrored_manifest_git = os.path.join(opt.reference, manifest_git_path)
if not mirrored_manifest_git.endswith(".git"):
mirrored_manifest_git += ".git"
if not os.path.exists(mirrored_manifest_git):
mirrored_manifest_git = os.path.join(opt.reference + '/.repo/manifests.git')
m._InitGitDir(mirror_git=mirrored_manifest_git)
if opt.manifest_branch:
m.revisionExpr = opt.manifest_branch
@ -125,19 +178,53 @@ to update the working directory files.
r.ResetFetch()
r.Save()
groups = re.split(r'[,\s]+', opt.groups)
all_platforms = ['linux', 'darwin']
platformize = lambda x: 'platform-' + x
if opt.platform == 'auto':
if (not opt.mirror and
not m.config.GetString('repo.mirror') == 'true'):
groups.append(platformize(platform.system().lower()))
elif opt.platform == 'all':
groups.extend(map(platformize, all_platforms))
elif opt.platform in all_platforms:
groups.extend(platformize(opt.platform))
elif opt.platform != 'none':
print('fatal: invalid platform flag', file=sys.stderr)
sys.exit(1)
groups = [x for x in groups if x]
groupstr = ','.join(groups)
if opt.platform == 'auto' and groupstr == 'default,platform-' + platform.system().lower():
groupstr = None
m.config.SetString('manifest.groups', groupstr)
if opt.reference:
m.config.SetString('repo.reference', opt.reference)
if opt.archive:
if is_new:
m.config.SetString('repo.archive', 'true')
else:
print('fatal: --archive is only supported when initializing a new '
'workspace.', file=sys.stderr)
print('Either delete the .repo folder in this workspace, or initialize '
'in another location.', file=sys.stderr)
sys.exit(1)
if opt.mirror:
if is_new:
m.config.SetString('repo.mirror', 'true')
else:
print >>sys.stderr, 'fatal: --mirror not supported on existing client'
print('fatal: --mirror is only supported when initializing a new '
'workspace.', file=sys.stderr)
print('Either delete the .repo folder in this workspace, or initialize '
'in another location.', file=sys.stderr)
sys.exit(1)
if not m.Sync_NetworkHalf():
if not m.Sync_NetworkHalf(is_new=is_new):
r = m.GetRemote(m.remote.name)
print >>sys.stderr, 'fatal: cannot obtain manifest %s' % r.url
print('fatal: cannot obtain manifest %s' % r.url, file=sys.stderr)
# Better delete the manifest git dir if we created it; otherwise next
# time (when user fixes problems) we won't go through the "is_new" logic.
@ -145,48 +232,67 @@ to update the working directory files.
shutil.rmtree(m.gitdir)
sys.exit(1)
if opt.manifest_branch:
m.MetaBranchSwitch()
syncbuf = SyncBuffer(m.config)
m.Sync_LocalHalf(syncbuf)
syncbuf.Finish()
if is_new or m.CurrentBranch is None:
if not m.StartBranch('default'):
print >>sys.stderr, 'fatal: cannot create default in manifest'
print('fatal: cannot create default in manifest', file=sys.stderr)
sys.exit(1)
def _LinkManifest(self, name):
if not name:
print >>sys.stderr, 'fatal: manifest name (-m) is required.'
print('fatal: manifest name (-m) is required.', file=sys.stderr)
sys.exit(1)
try:
self.manifest.Link(name)
except ManifestParseError, e:
print >>sys.stderr, "fatal: manifest '%s' not available" % name
print >>sys.stderr, 'fatal: %s' % str(e)
except ManifestParseError as e:
print("fatal: manifest '%s' not available" % name, file=sys.stderr)
print('fatal: %s' % str(e), file=sys.stderr)
sys.exit(1)
def _Prompt(self, prompt, value):
mp = self.manifest.manifestProject
sys.stdout.write('%-10s [%s]: ' % (prompt, value))
a = sys.stdin.readline().strip()
if a == '':
return value
return a
def _ShouldConfigureUser(self):
gc = self.manifest.globalConfig
mp = self.manifest.manifestProject
# If we don't have local settings, get from global.
if not mp.config.Has('user.name') or not mp.config.Has('user.email'):
if not gc.Has('user.name') or not gc.Has('user.email'):
return True
mp.config.SetString('user.name', gc.GetString('user.name'))
mp.config.SetString('user.email', gc.GetString('user.email'))
print()
print('Your identity is: %s <%s>' % (mp.config.GetString('user.name'),
mp.config.GetString('user.email')))
print('If you want to change this, please re-run \'repo init\' with --config-name')
return False
def _ConfigureUser(self):
mp = self.manifest.manifestProject
while True:
print ''
print()
name = self._Prompt('Your Name', mp.UserName)
email = self._Prompt('Your Email', mp.UserEmail)
print ''
print 'Your identity is: %s <%s>' % (name, email)
sys.stdout.write('is this correct [y/n]? ')
a = sys.stdin.readline().strip()
print()
print('Your identity is: %s <%s>' % (name, email))
sys.stdout.write('is this correct [y/N]? ')
a = sys.stdin.readline().strip().lower()
if a in ('yes', 'y', 't', 'true'):
break
@ -212,39 +318,83 @@ to update the working directory files.
self._on = True
out = _Test()
print ''
print "Testing colorized output (for 'repo diff', 'repo status'):"
print()
print("Testing colorized output (for 'repo diff', 'repo status'):")
for c in ['black','red','green','yellow','blue','magenta','cyan']:
for c in ['black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan']:
out.write(' ')
out.printer(fg=c)(' %-6s ', c)
out.write(' ')
out.printer(fg='white', bg='black')(' %s ' % 'white')
out.nl()
for c in ['bold','dim','ul','reverse']:
for c in ['bold', 'dim', 'ul', 'reverse']:
out.write(' ')
out.printer(fg='black', attr=c)(' %-6s ', c)
out.nl()
sys.stdout.write('Enable color display in this user account (y/n)? ')
sys.stdout.write('Enable color display in this user account (y/N)? ')
a = sys.stdin.readline().strip().lower()
if a in ('y', 'yes', 't', 'true', 'on'):
gc.SetString('color.ui', 'auto')
def _ConfigureDepth(self, opt):
"""Configure the depth we'll sync down.
Args:
opt: Options from optparse. We care about opt.depth.
"""
# Opt.depth will be non-None if user actually passed --depth to repo init.
if opt.depth is not None:
if opt.depth > 0:
# Positive values will set the depth.
depth = str(opt.depth)
else:
# Negative numbers will clear the depth; passing None to SetString
# will do that.
depth = None
# We store the depth in the main manifest project.
self.manifest.manifestProject.config.SetString('repo.depth', depth)
def _DisplayResult(self):
if self.manifest.IsMirror:
init_type = 'mirror '
else:
init_type = ''
print()
print('repo %shas been initialized in %s'
% (init_type, self.manifest.topdir))
current_dir = os.getcwd()
if current_dir != self.manifest.topdir:
print('If this is not the directory in which you want to initialize '
'repo, please run:')
print(' rm -r %s/.repo' % self.manifest.topdir)
print('and try again.')
def Execute(self, opt, args):
git_require(MIN_GIT_VERSION, fail=True)
if opt.reference:
opt.reference = os.path.expanduser(opt.reference)
# Check this here, else manifest will be tagged "not new" and init won't be
# possible anymore without removing the .repo/manifests directory.
if opt.archive and opt.mirror:
print('fatal: --mirror and --archive cannot be used together.',
file=sys.stderr)
sys.exit(1)
self._SyncManifest(opt)
self._LinkManifest(opt.manifest_name)
if os.isatty(0) and os.isatty(1) and not self.manifest.IsMirror:
self._ConfigureUser()
if opt.config_name or self._ShouldConfigureUser():
self._ConfigureUser()
self._ConfigureColor()
if self.manifest.IsMirror:
type = 'mirror '
else:
type = ''
self._ConfigureDepth(opt)
print ''
print 'repo %sinitialized in %s' % (type, self.manifest.topdir)
self._DisplayResult()

View File

@ -13,13 +13,17 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import print_function
import sys
from command import Command, MirrorSafeCommand
class List(Command, MirrorSafeCommand):
common = True
helpSummary = "List projects and their associated directories"
helpUsage = """
%prog [<project>...]
%prog [-f] [<project>...]
%prog [-f] -r str1 [str2]..."
"""
helpDescription = """
List all projects; pass '.' to list the project for the cwd.
@ -27,6 +31,20 @@ List all projects; pass '.' to list the project for the cwd.
This is similar to running: repo forall -c 'echo "$REPO_PATH : $REPO_PROJECT"'.
"""
def _Options(self, p):
p.add_option('-r', '--regex',
dest='regex', action='store_true',
help="Filter the project list based on regex or wildcard matching of strings")
p.add_option('-f', '--fullpath',
dest='fullpath', action='store_true',
help="Display the full work tree path instead of the relative path")
p.add_option('-n', '--name-only',
dest='name_only', action='store_true',
help="Display only the name of the repository")
p.add_option('-p', '--path-only',
dest='path_only', action='store_true',
help="Display only the path of the repository")
def Execute(self, opt, args):
"""List all projects and the associated directories.
@ -35,14 +53,32 @@ This is similar to running: repo forall -c 'echo "$REPO_PATH : $REPO_PROJECT"'.
discoverable.
Args:
opt: The options. We don't take any.
opt: The options.
args: Positional args. Can be a list of projects to list, or empty.
"""
projects = self.GetProjects(args)
if opt.fullpath and opt.name_only:
print('error: cannot combine -f and -n', file=sys.stderr)
sys.exit(1)
if not opt.regex:
projects = self.GetProjects(args)
else:
projects = self.FindProjects(args)
def _getpath(x):
if opt.fullpath:
return x.worktree
return x.relpath
lines = []
for project in projects:
lines.append("%s : %s" % (project.relpath, project.name))
if opt.name_only and not opt.path_only:
lines.append("%s" % ( project.name))
elif opt.path_only and not opt.name_only:
lines.append("%s" % (_getpath(project)))
else:
lines.append("%s : %s" % (_getpath(project), project.name))
lines.sort()
print '\n'.join(lines)
print('\n'.join(lines))

View File

@ -13,6 +13,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import print_function
import os
import sys
@ -35,21 +36,27 @@ in a Git repository for use during future 'repo init' invocations.
@property
def helpDescription(self):
help = self._helpDescription + '\n'
helptext = self._helpDescription + '\n'
r = os.path.dirname(__file__)
r = os.path.dirname(r)
fd = open(os.path.join(r, 'docs', 'manifest-format.txt'))
for line in fd:
help += line
helptext += line
fd.close()
return help
return helptext
def _Options(self, p):
p.add_option('-r', '--revision-as-HEAD',
dest='peg_rev', action='store_true',
help='Save revisions as current HEAD')
p.add_option('--suppress-upstream-revision', dest='peg_rev_upstream',
default=True, action='store_false',
help='If in -r mode, do not write the upstream field. '
'Only of use if the branch names for a sha1 manifest are '
'sensitive.')
p.add_option('-o', '--output-file',
dest='output_file',
default='-',
help='File to save the manifest to',
metavar='-|NAME.xml')
@ -59,10 +66,11 @@ in a Git repository for use during future 'repo init' invocations.
else:
fd = open(opt.output_file, 'w')
self.manifest.Save(fd,
peg_rev = opt.peg_rev)
peg_rev = opt.peg_rev,
peg_rev_upstream = opt.peg_rev_upstream)
fd.close()
if opt.output_file != '-':
print >>sys.stderr, 'Saved manifest to %s' % opt.output_file
print('Saved manifest to %s' % opt.output_file, file=sys.stderr)
def Execute(self, opt, args):
if args:
@ -72,6 +80,6 @@ in a Git repository for use during future 'repo init' invocations.
self._Output(opt)
return
print >>sys.stderr, 'error: no operation to perform'
print >>sys.stderr, 'error: see repo help manifest'
print('error: no operation to perform', file=sys.stderr)
print('error: see repo help manifest', file=sys.stderr)
sys.exit(1)

84
subcmds/overview.py Normal file
View File

@ -0,0 +1,84 @@
#
# Copyright (C) 2012 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import print_function
from color import Coloring
from command import PagedCommand
class Overview(PagedCommand):
common = True
helpSummary = "Display overview of unmerged project branches"
helpUsage = """
%prog [--current-branch] [<project>...]
"""
helpDescription = """
The '%prog' command is used to display an overview of the projects branches,
and list any local commits that have not yet been merged into the project.
The -b/--current-branch option can be used to restrict the output to only
branches currently checked out in each project. By default, all branches
are displayed.
"""
def _Options(self, p):
p.add_option('-b', '--current-branch',
dest="current_branch", action="store_true",
help="Consider only checked out branches")
def Execute(self, opt, args):
all_branches = []
for project in self.GetProjects(args):
br = [project.GetUploadableBranch(x)
for x in project.GetBranches()]
br = [x for x in br if x]
if opt.current_branch:
br = [x for x in br if x.name == project.CurrentBranch]
all_branches.extend(br)
if not all_branches:
return
class Report(Coloring):
def __init__(self, config):
Coloring.__init__(self, config, 'status')
self.project = self.printer('header', attr='bold')
self.text = self.printer('text')
out = Report(all_branches[0].project.config)
out.text("Deprecated. See repo info -o.")
out.nl()
out.project('Projects Overview')
out.nl()
project = None
for branch in all_branches:
if project != branch.project:
project = branch.project
out.nl()
out.project('project %s/' % project.relpath)
out.nl()
commits = branch.commits
date = branch.date
print('%s %-33s (%2d commit%s, %s)' % (
branch.name == project.CurrentBranch and '*' or ' ',
branch.name,
len(commits),
len(commits) != 1 and 's' or ' ',
date))
for commit in commits:
print('%-35s - %s' % ('', commit))

View File

@ -13,6 +13,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import print_function
from color import Coloring
from command import PagedCommand
@ -24,11 +25,11 @@ class Prune(PagedCommand):
"""
def Execute(self, opt, args):
all = []
all_branches = []
for project in self.GetProjects(args):
all.extend(project.PruneHeads())
all_branches.extend(project.PruneHeads())
if not all:
if not all_branches:
return
class Report(Coloring):
@ -36,13 +37,13 @@ class Prune(PagedCommand):
Coloring.__init__(self, config, 'status')
self.project = self.printer('header', attr='bold')
out = Report(all[0].project.config)
out = Report(all_branches[0].project.config)
out.project('Pending Branches')
out.nl()
project = None
for branch in all:
for branch in all_branches:
if project != branch.project:
project = branch.project
out.nl()
@ -51,9 +52,9 @@ class Prune(PagedCommand):
commits = branch.commits
date = branch.date
print '%s %-33s (%2d commit%s, %s)' % (
print('%s %-33s (%2d commit%s, %s)' % (
branch.name == project.CurrentBranch and '*' or ' ',
branch.name,
len(commits),
len(commits) != 1 and 's' or ' ',
date)
date))

View File

@ -13,12 +13,11 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import print_function
import sys
from command import Command
from git_command import GitCommand
from git_refs import GitRefs, HEAD, R_HEADS, R_TAGS, R_PUB, R_M
from error import GitError
class Rebase(Command):
common = True
@ -52,20 +51,28 @@ branch but need to incorporate new upstream changes "underneath" them.
p.add_option('--whitespace',
dest='whitespace', action='store', metavar='WS',
help='Pass --whitespace to git rebase')
p.add_option('--auto-stash',
dest='auto_stash', action='store_true',
help='Stash local modifications before starting')
def Execute(self, opt, args):
all = self.GetProjects(args)
one_project = len(all) == 1
all_projects = self.GetProjects(args)
one_project = len(all_projects) == 1
if opt.interactive and not one_project:
print >>sys.stderr, 'error: interactive rebase not supported with multiple projects'
print('error: interactive rebase not supported with multiple projects',
file=sys.stderr)
if len(args) == 1:
print('note: project %s is mapped to more than one path' % (args[0],),
file=sys.stderr)
return -1
for project in all:
for project in all_projects:
cb = project.CurrentBranch
if not cb:
if one_project:
print >>sys.stderr, "error: project %s has a detatched HEAD" % project.relpath
print("error: project %s has a detached HEAD" % project.relpath,
file=sys.stderr)
return -1
# ignore branches with detatched HEADs
continue
@ -73,7 +80,8 @@ branch but need to incorporate new upstream changes "underneath" them.
upbranch = project.GetBranch(cb)
if not upbranch.LocalMerge:
if one_project:
print >>sys.stderr, "error: project %s does not track any remote branches" % project.relpath
print("error: project %s does not track any remote branches"
% project.relpath, file=sys.stderr)
return -1
# ignore branches without remotes
continue
@ -100,8 +108,26 @@ branch but need to incorporate new upstream changes "underneath" them.
args.append(upbranch.LocalMerge)
print >>sys.stderr, '# %s: rebasing %s -> %s' % \
(project.relpath, cb, upbranch.LocalMerge)
print('# %s: rebasing %s -> %s'
% (project.relpath, cb, upbranch.LocalMerge), file=sys.stderr)
needs_stash = False
if opt.auto_stash:
stash_args = ["update-index", "--refresh", "-q"]
if GitCommand(project, stash_args).Wait() != 0:
needs_stash = True
# Dirty index, requires stash...
stash_args = ["stash"]
if GitCommand(project, stash_args).Wait() != 0:
return -1
if GitCommand(project, args).Wait() != 0:
return -1
if needs_stash:
stash_args.append('pop')
stash_args.append('--quiet')
if GitCommand(project, stash_args).Wait() != 0:
return -1

View File

@ -13,6 +13,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import print_function
from optparse import SUPPRESS_HELP
import sys
@ -52,7 +53,7 @@ need to be performed by an end-user.
else:
if not rp.Sync_NetworkHalf():
print >>sys.stderr, "error: can't update repo"
print("error: can't update repo", file=sys.stderr)
sys.exit(1)
rp.bare_git.gc('--auto')

View File

@ -13,7 +13,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from sync import Sync
from subcmds.sync import Sync
class Smartsync(Sync):
common = True

View File

@ -13,6 +13,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import print_function
import sys
from color import Coloring
@ -48,9 +49,9 @@ The '%prog' command stages files to prepare the next commit.
self.Usage()
def _Interactive(self, opt, args):
all = filter(lambda x: x.IsDirty(), self.GetProjects(args))
if not all:
print >>sys.stderr,'no projects have uncommitted modifications'
all_projects = [p for p in self.GetProjects(args) if p.IsDirty()]
if not all_projects:
print('no projects have uncommitted modifications', file=sys.stderr)
return
out = _ProjectList(self.manifest.manifestProject.config)
@ -58,8 +59,8 @@ The '%prog' command stages files to prepare the next commit.
out.header(' %s', 'project')
out.nl()
for i in xrange(0, len(all)):
p = all[i]
for i in range(len(all_projects)):
p = all_projects[i]
out.write('%3d: %s', i + 1, p.relpath + '/')
out.nl()
out.nl()
@ -93,15 +94,15 @@ The '%prog' command stages files to prepare the next commit.
if a_index is not None:
if a_index == 0:
break
if 0 < a_index and a_index <= len(all):
_AddI(all[a_index - 1])
if 0 < a_index and a_index <= len(all_projects):
_AddI(all_projects[a_index - 1])
continue
p = filter(lambda x: x.name == a or x.relpath == a, all)
if len(p) == 1:
_AddI(p[0])
projects = [p for p in all_projects if a in [p.name, p.relpath]]
if len(projects) == 1:
_AddI(projects[0])
continue
print 'Bye.'
print('Bye.')
def _AddI(project):
p = GitCommand(project, ['add', '--interactive'], bare=False)

View File

@ -13,8 +13,10 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import print_function
import sys
from command import Command
from git_config import IsId
from git_command import git
from progress import Progress
@ -40,7 +42,7 @@ revision specified in the manifest.
nb = args[0]
if not git.check_ref_format('heads/%s' % nb):
print >>sys.stderr, "error: '%s' is not a valid name" % nb
print("error: '%s' is not a valid name" % nb, file=sys.stderr)
sys.exit(1)
err = []
@ -48,21 +50,28 @@ revision specified in the manifest.
if not opt.all:
projects = args[1:]
if len(projects) < 1:
print >>sys.stderr, "error: at least one project must be specified"
print("error: at least one project must be specified", file=sys.stderr)
sys.exit(1)
all = self.GetProjects(projects)
all_projects = self.GetProjects(projects)
pm = Progress('Starting %s' % nb, len(all))
for project in all:
pm = Progress('Starting %s' % nb, len(all_projects))
for project in all_projects:
pm.update()
# If the current revision is a specific SHA1 then we can't push back
# to it; so substitute with dest_branch if defined, or with manifest
# default revision instead.
if IsId(project.revisionExpr):
if project.dest_branch:
project.revisionExpr = project.dest_branch
else:
project.revisionExpr = self.manifest.default.revisionExpr
if not project.StartBranch(nb):
err.append(project)
pm.end()
if err:
for p in err:
print >>sys.stderr,\
"error: %s/: cannot start %s" \
% (p.relpath, nb)
print("error: %s/: cannot start %s" % (p.relpath, nb),
file=sys.stderr)
sys.exit(1)

View File

@ -20,9 +20,12 @@ try:
except ImportError:
import dummy_threading as _threading
import glob
import itertools
import sys
import StringIO
import os
from color import Coloring
class Status(PagedCommand):
common = True
@ -39,6 +42,13 @@ is a difference between these three states.
The -j/--jobs option can be used to run multiple status queries
in parallel.
The -o/--orphans option can be used to show objects that are in
the working directory, but not associated with a repo project.
This includes unmanaged top-level files and directories, but also
includes deeper items. For example, if dir/subdir/proj1 and
dir/subdir/proj2 are repo projects, dir/subdir/proj3 will be shown
if it is not known to repo.
Status Display
--------------
@ -76,8 +86,11 @@ the following meanings:
p.add_option('-j', '--jobs',
dest='jobs', action='store', type='int', default=2,
help="number of projects to check simultaneously")
p.add_option('-o', '--orphans',
dest='orphans', action='store_true',
help="include objects in working directory outside of repo projects")
def _StatusHelper(self, project, clean_counter, sem, output):
def _StatusHelper(self, project, clean_counter, sem):
"""Obtains the status for a specific project.
Obtains the status for a project, redirecting the output to
@ -91,47 +104,89 @@ the following meanings:
output: Where to output the status.
"""
try:
state = project.PrintWorkTreeStatus(output)
state = project.PrintWorkTreeStatus()
if state == 'CLEAN':
clean_counter.next()
next(clean_counter)
finally:
sem.release()
def _FindOrphans(self, dirs, proj_dirs, proj_dirs_parents, outstring):
"""find 'dirs' that are present in 'proj_dirs_parents' but not in 'proj_dirs'"""
status_header = ' --\t'
for item in dirs:
if not os.path.isdir(item):
outstring.append(''.join([status_header, item]))
continue
if item in proj_dirs:
continue
if item in proj_dirs_parents:
self._FindOrphans(glob.glob('%s/.*' % item) +
glob.glob('%s/*' % item),
proj_dirs, proj_dirs_parents, outstring)
continue
outstring.append(''.join([status_header, item, '/']))
def Execute(self, opt, args):
all = self.GetProjects(args)
all_projects = self.GetProjects(args)
counter = itertools.count()
on = {}
for project in all:
cb = project.CurrentBranch
if cb:
if cb not in on:
on[cb] = []
on[cb].append(project)
branch_names = list(on.keys())
branch_names.sort()
for cb in branch_names:
print '# on branch %s' % cb
if opt.jobs == 1:
for project in all:
for project in all_projects:
state = project.PrintWorkTreeStatus()
if state == 'CLEAN':
counter.next()
next(counter)
else:
sem = _threading.Semaphore(opt.jobs)
threads_and_output = []
for project in all:
threads = []
for project in all_projects:
sem.acquire()
output = StringIO.StringIO()
t = _threading.Thread(target=self._StatusHelper,
args=(project, counter, sem, output))
threads_and_output.append((t, output))
args=(project, counter, sem))
threads.append(t)
t.daemon = True
t.start()
for (t, output) in threads_and_output:
for t in threads:
t.join()
sys.stdout.write(output.getvalue())
output.close()
if len(all) == counter.next():
print 'nothing to commit (working directory clean)'
if len(all_projects) == next(counter):
print('nothing to commit (working directory clean)')
if opt.orphans:
proj_dirs = set()
proj_dirs_parents = set()
for project in self.GetProjects(None, missing_ok=True):
proj_dirs.add(project.relpath)
(head, _tail) = os.path.split(project.relpath)
while head != "":
proj_dirs_parents.add(head)
(head, _tail) = os.path.split(head)
proj_dirs.add('.repo')
class StatusColoring(Coloring):
def __init__(self, config):
Coloring.__init__(self, config, 'status')
self.project = self.printer('header', attr = 'bold')
self.untracked = self.printer('untracked', fg = 'red')
orig_path = os.getcwd()
try:
os.chdir(self.manifest.topdir)
outstring = []
self._FindOrphans(glob.glob('.*') +
glob.glob('*'),
proj_dirs, proj_dirs_parents, outstring)
if outstring:
output = StatusColoring(self.manifest.globalConfig)
output.project('Objects not within a project (orphans)')
output.nl()
for entry in outstring:
output.untracked(entry)
output.nl()
else:
print('No orphan files or directories')
finally:
# Restore CWD.
os.chdir(orig_path)

View File

@ -13,6 +13,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import print_function
import json
import netrc
from optparse import SUPPRESS_HELP
import os
import re
@ -21,23 +24,49 @@ import socket
import subprocess
import sys
import time
import xmlrpclib
from pyversion import is_python3
if is_python3():
import urllib.parse
import xmlrpc.client
else:
import imp
import urlparse
import xmlrpclib
urllib = imp.new_module('urllib')
urllib.parse = urlparse
xmlrpc = imp.new_module('xmlrpc')
xmlrpc.client = xmlrpclib
try:
import threading as _threading
except ImportError:
import dummy_threading as _threading
from git_command import GIT
from git_refs import R_HEADS
from project import HEAD
try:
import resource
def _rlimit_nofile():
return resource.getrlimit(resource.RLIMIT_NOFILE)
except ImportError:
def _rlimit_nofile():
return (256, 256)
try:
import multiprocessing
except ImportError:
multiprocessing = None
from git_command import GIT, git_require
from git_refs import R_HEADS, HEAD
from project import Project
from project import RemoteSpec
from command import Command, MirrorSafeCommand
from error import RepoChangedException, GitError
from project import R_HEADS
from error import RepoChangedException, GitError, ManifestParseError
from project import SyncBuffer
from progress import Progress
from wrapper import Wrapper
_ONE_DAY_S = 24 * 60 * 60
class _FetchError(Exception):
"""Internal error thrown in _FetchHelper() when we don't want stack trace."""
@ -72,11 +101,40 @@ revision is temporarily needed.
The -s/--smart-sync option can be used to sync to a known good
build as specified by the manifest-server element in the current
manifest.
manifest. The -t/--smart-tag option is similar and allows you to
specify a custom tag/label.
The -u/--manifest-server-username and -p/--manifest-server-password
options can be used to specify a username and password to authenticate
with the manifest server when using the -s or -t option.
If -u and -p are not specified when using the -s or -t option, '%prog'
will attempt to read authentication credentials for the manifest server
from the user's .netrc file.
'%prog' will not use authentication credentials from -u/-p or .netrc
if the manifest server specified in the manifest file already includes
credentials.
The -f/--force-broken option can be used to proceed with syncing
other projects if a project sync fails.
The --no-clone-bundle option disables any attempt to use
$URL/clone.bundle to bootstrap a new Git repository from a
resumeable bundle file on a content delivery network. This
may be necessary if there are problems with the local Python
HTTP client or proxy configuration, but the Git binary works.
The --fetch-submodules option enables fetching Git submodules
of a project from server.
The -c/--current-branch option can be used to only fetch objects that
are on the branch specified by a project's revision.
The --optimized-fetch option can be used to only fetch projects that
are fixed to a sha1 revision if the sha1 revision does not already
exist locally.
SSH Connections
---------------
@ -108,28 +166,60 @@ later is required to fix a server side protocol bug.
"""
def _Options(self, p, show_smart=True):
try:
self.jobs = self.manifest.default.sync_j
except ManifestParseError:
self.jobs = 1
p.add_option('-f', '--force-broken',
dest='force_broken', action='store_true',
help="continue sync even if a project fails to sync")
p.add_option('-l','--local-only',
p.add_option('-l', '--local-only',
dest='local_only', action='store_true',
help="only update working tree, don't fetch")
p.add_option('-n','--network-only',
p.add_option('-n', '--network-only',
dest='network_only', action='store_true',
help="fetch only, don't update working tree")
p.add_option('-d','--detach',
p.add_option('-d', '--detach',
dest='detach_head', action='store_true',
help='detach projects back to manifest revision')
p.add_option('-q','--quiet',
p.add_option('-c', '--current-branch',
dest='current_branch_only', action='store_true',
help='fetch only current branch from server')
p.add_option('-q', '--quiet',
dest='quiet', action='store_true',
help='be more quiet')
p.add_option('-j','--jobs',
p.add_option('-j', '--jobs',
dest='jobs', action='store', type='int',
help="number of projects to fetch simultaneously")
help="projects to fetch simultaneously (default %d)" % self.jobs)
p.add_option('-m', '--manifest-name',
dest='manifest_name',
help='temporary manifest to use for this sync', metavar='NAME.xml')
p.add_option('--no-clone-bundle',
dest='no_clone_bundle', action='store_true',
help='disable use of /clone.bundle on HTTP/HTTPS')
p.add_option('-u', '--manifest-server-username', action='store',
dest='manifest_server_username',
help='username to authenticate with the manifest server')
p.add_option('-p', '--manifest-server-password', action='store',
dest='manifest_server_password',
help='password to authenticate with the manifest server')
p.add_option('--fetch-submodules',
dest='fetch_submodules', action='store_true',
help='fetch submodules from server')
p.add_option('--no-tags',
dest='no_tags', action='store_true',
help="don't fetch tags")
p.add_option('--optimized-fetch',
dest='optimized_fetch', action='store_true',
help='only fetch projects fixed to sha1 if revision does not exist locally')
if show_smart:
p.add_option('-s', '--smart-sync',
dest='smart_sync', action='store_true',
help='smart sync using manifest from a known good build')
p.add_option('-t', '--smart-tag',
dest='smart_tag', action='store',
help='smart sync using manifest from a known tag')
g = p.add_option_group('repo Version options')
g.add_option('--no-repo-verify',
@ -139,116 +229,203 @@ later is required to fix a server side protocol bug.
dest='repo_upgraded', action='store_true',
help=SUPPRESS_HELP)
def _FetchProjectList(self, opt, projects, *args, **kwargs):
"""Main function of the fetch threads when jobs are > 1.
Delegates most of the work to _FetchHelper.
Args:
opt: Program options returned from optparse. See _Options().
projects: Projects to fetch.
*args, **kwargs: Remaining arguments to pass to _FetchHelper. See the
_FetchHelper docstring for details.
"""
for project in projects:
success = self._FetchHelper(opt, project, *args, **kwargs)
if not success and not opt.force_broken:
break
def _FetchHelper(self, opt, project, lock, fetched, pm, sem, err_event):
"""Main function of the fetch threads when jobs are > 1.
"""Fetch git objects for a single project.
Args:
opt: Program options returned from optparse. See _Options().
project: Project object for the project to fetch.
lock: Lock for accessing objects that are shared amongst multiple
_FetchHelper() threads.
fetched: set object that we will add project.gitdir to when we're done
(with our lock held).
pm: Instance of a Project object. We will call pm.update() (with our
lock held).
sem: We'll release() this semaphore when we exit so that another thread
can be started up.
err_event: We'll set this event in the case of an error (after printing
out info about the error).
"""
# We'll set to true once we've locked the lock.
did_lock = False
Args:
opt: Program options returned from optparse. See _Options().
project: Project object for the project to fetch.
lock: Lock for accessing objects that are shared amongst multiple
_FetchHelper() threads.
fetched: set object that we will add project.gitdir to when we're done
(with our lock held).
pm: Instance of a Project object. We will call pm.update() (with our
lock held).
sem: We'll release() this semaphore when we exit so that another thread
can be started up.
err_event: We'll set this event in the case of an error (after printing
out info about the error).
# Encapsulate everything in a try/except/finally so that:
# - We always set err_event in the case of an exception.
# - We always make sure we call sem.release().
# - We always make sure we unlock the lock if we locked it.
Returns:
Whether the fetch was successful.
"""
# We'll set to true once we've locked the lock.
did_lock = False
if not opt.quiet:
print('Fetching project %s' % project.name)
# Encapsulate everything in a try/except/finally so that:
# - We always set err_event in the case of an exception.
# - We always make sure we call sem.release().
# - We always make sure we unlock the lock if we locked it.
try:
try:
try:
success = project.Sync_NetworkHalf(quiet=opt.quiet)
start = time.time()
success = project.Sync_NetworkHalf(
quiet=opt.quiet,
current_branch_only=opt.current_branch_only,
clone_bundle=not opt.no_clone_bundle,
no_tags=opt.no_tags, archive=self.manifest.IsArchive,
optimized_fetch=opt.optimized_fetch)
self._fetch_times.Set(project, time.time() - start)
# Lock around all the rest of the code, since printing, updating a set
# and Progress.update() are not thread safe.
lock.acquire()
did_lock = True
# Lock around all the rest of the code, since printing, updating a set
# and Progress.update() are not thread safe.
lock.acquire()
did_lock = True
if not success:
print >>sys.stderr, 'error: Cannot fetch %s' % project.name
if opt.force_broken:
print >>sys.stderr, 'warn: --force-broken, continuing to sync'
else:
raise _FetchError()
if not success:
print('error: Cannot fetch %s' % project.name, file=sys.stderr)
if opt.force_broken:
print('warn: --force-broken, continuing to sync',
file=sys.stderr)
else:
raise _FetchError()
fetched.add(project.gitdir)
pm.update()
except BaseException, e:
# Notify the _Fetch() function about all errors.
err_event.set()
fetched.add(project.gitdir)
pm.update()
except _FetchError:
err_event.set()
except:
err_event.set()
raise
finally:
if did_lock:
lock.release()
sem.release()
# If we got our own _FetchError, we don't want a stack trace.
# However, if we got something else (something in Sync_NetworkHalf?),
# we'd like one (so re-raise after we've set err_event).
if not isinstance(e, _FetchError):
raise
finally:
if did_lock:
lock.release()
sem.release()
return success
def _Fetch(self, projects, opt):
fetched = set()
lock = _threading.Lock()
pm = Progress('Fetching projects', len(projects))
if self.jobs == 1:
for project in projects:
pm.update()
if project.Sync_NetworkHalf(quiet=opt.quiet):
fetched.add(project.gitdir)
else:
print >>sys.stderr, 'error: Cannot fetch %s' % project.name
if opt.force_broken:
print >>sys.stderr, 'warn: --force-broken, continuing to sync'
else:
sys.exit(1)
else:
threads = set()
lock = _threading.Lock()
sem = _threading.Semaphore(self.jobs)
err_event = _threading.Event()
for project in projects:
# Check for any errors before starting any new threads.
# ...we'll let existing threads finish, though.
if err_event.isSet():
break
objdir_project_map = dict()
for project in projects:
objdir_project_map.setdefault(project.objdir, []).append(project)
sem.acquire()
t = _threading.Thread(target = self._FetchHelper,
args = (opt,
project,
lock,
fetched,
pm,
sem,
err_event))
threads = set()
sem = _threading.Semaphore(self.jobs)
err_event = _threading.Event()
for project_list in objdir_project_map.values():
# Check for any errors before running any more tasks.
# ...we'll let existing threads finish, though.
if err_event.isSet() and not opt.force_broken:
break
sem.acquire()
kwargs = dict(opt=opt,
projects=project_list,
lock=lock,
fetched=fetched,
pm=pm,
sem=sem,
err_event=err_event)
if self.jobs > 1:
t = _threading.Thread(target = self._FetchProjectList,
kwargs = kwargs)
# Ensure that Ctrl-C will not freeze the repo process.
t.daemon = True
threads.add(t)
t.start()
else:
self._FetchProjectList(**kwargs)
for t in threads:
t.join()
for t in threads:
t.join()
# If we saw an error, exit with code 1 so that other scripts can check.
if err_event.isSet():
print >>sys.stderr, '\nerror: Exited sync due to fetch errors'
sys.exit(1)
# If we saw an error, exit with code 1 so that other scripts can check.
if err_event.isSet():
print('\nerror: Exited sync due to fetch errors', file=sys.stderr)
sys.exit(1)
pm.end()
for project in projects:
project.bare_git.gc('--auto')
self._fetch_times.Save()
if not self.manifest.IsArchive:
self._GCProjects(projects)
return fetched
def _GCProjects(self, projects):
gitdirs = {}
for project in projects:
gitdirs[project.gitdir] = project.bare_git
has_dash_c = git_require((1, 7, 2))
if multiprocessing and has_dash_c:
cpu_count = multiprocessing.cpu_count()
else:
cpu_count = 1
jobs = min(self.jobs, cpu_count)
if jobs < 2:
for bare_git in gitdirs.values():
bare_git.gc('--auto')
return
config = {'pack.threads': cpu_count / jobs if cpu_count > jobs else 1}
threads = set()
sem = _threading.Semaphore(jobs)
err_event = _threading.Event()
def GC(bare_git):
try:
try:
bare_git.gc('--auto', config=config)
except GitError:
err_event.set()
except:
err_event.set()
raise
finally:
sem.release()
for bare_git in gitdirs.values():
if err_event.isSet():
break
sem.acquire()
t = _threading.Thread(target=GC, args=(bare_git,))
t.daemon = True
threads.add(t)
t.start()
for t in threads:
t.join()
if err_event.isSet():
print('\nerror: Exited sync due to gc errors', file=sys.stderr)
sys.exit(1)
def _ReloadManifest(self, manifest_name=None):
if manifest_name:
# Override calls _Unload already
self.manifest.Override(manifest_name)
else:
self.manifest._Unload()
def UpdateProjectList(self):
new_project_paths = []
for project in self.manifest.projects.values():
for project in self.GetProjects(None, missing_ok=True):
if project.relpath:
new_project_paths.append(project.relpath)
file_name = 'project.list'
@ -265,36 +442,39 @@ later is required to fix a server side protocol bug.
if not path:
continue
if path not in new_project_paths:
"""If the path has already been deleted, we don't need to do it
"""
# If the path has already been deleted, we don't need to do it
if os.path.exists(self.manifest.topdir + '/' + path):
project = Project(
manifest = self.manifest,
name = path,
remote = RemoteSpec('origin'),
gitdir = os.path.join(self.manifest.topdir,
path, '.git'),
worktree = os.path.join(self.manifest.topdir, path),
relpath = path,
revisionExpr = 'HEAD',
revisionId = None)
gitdir = os.path.join(self.manifest.topdir, path, '.git')
project = Project(
manifest = self.manifest,
name = path,
remote = RemoteSpec('origin'),
gitdir = gitdir,
objdir = gitdir,
worktree = os.path.join(self.manifest.topdir, path),
relpath = path,
revisionExpr = 'HEAD',
revisionId = None,
groups = None)
if project.IsDirty():
print >>sys.stderr, 'error: Cannot remove project "%s": \
uncommitted changes are present' % project.relpath
print >>sys.stderr, ' commit changes, then run sync again'
return -1
else:
print >>sys.stderr, 'Deleting obsolete path %s' % project.worktree
shutil.rmtree(project.worktree)
# Try deleting parent subdirs if they are empty
dir = os.path.dirname(project.worktree)
while dir != self.manifest.topdir:
try:
os.rmdir(dir)
except OSError:
break
dir = os.path.dirname(dir)
if project.IsDirty():
print('error: Cannot remove project "%s": uncommitted changes '
'are present' % project.relpath, file=sys.stderr)
print(' commit changes, then run sync again',
file=sys.stderr)
return -1
else:
print('Deleting obsolete path %s' % project.worktree,
file=sys.stderr)
shutil.rmtree(project.worktree)
# Try deleting parent subdirs if they are empty
project_dir = os.path.dirname(project.worktree)
while project_dir != self.manifest.topdir:
try:
os.rmdir(project_dir)
except OSError:
break
project_dir = os.path.dirname(project_dir)
new_project_paths.sort()
fd = open(file_path, 'w')
@ -308,57 +488,137 @@ uncommitted changes are present' % project.relpath
def Execute(self, opt, args):
if opt.jobs:
self.jobs = opt.jobs
if self.jobs > 1:
soft_limit, _ = _rlimit_nofile()
self.jobs = min(self.jobs, (soft_limit - 5) / 3)
if opt.network_only and opt.detach_head:
print >>sys.stderr, 'error: cannot combine -n and -d'
print('error: cannot combine -n and -d', file=sys.stderr)
sys.exit(1)
if opt.network_only and opt.local_only:
print >>sys.stderr, 'error: cannot combine -n and -l'
print('error: cannot combine -n and -l', file=sys.stderr)
sys.exit(1)
if opt.smart_sync:
if not self.manifest.manifest_server:
print >>sys.stderr, \
'error: cannot smart sync: no manifest server defined in manifest'
if opt.manifest_name and opt.smart_sync:
print('error: cannot combine -m and -s', file=sys.stderr)
sys.exit(1)
if opt.manifest_name and opt.smart_tag:
print('error: cannot combine -m and -t', file=sys.stderr)
sys.exit(1)
if opt.manifest_server_username or opt.manifest_server_password:
if not (opt.smart_sync or opt.smart_tag):
print('error: -u and -p may only be combined with -s or -t',
file=sys.stderr)
sys.exit(1)
if None in [opt.manifest_server_username, opt.manifest_server_password]:
print('error: both -u and -p must be given', file=sys.stderr)
sys.exit(1)
try:
server = xmlrpclib.Server(self.manifest.manifest_server)
p = self.manifest.manifestProject
b = p.GetBranch(p.CurrentBranch)
branch = b.merge
if branch.startswith(R_HEADS):
branch = branch[len(R_HEADS):]
env = os.environ.copy()
if (env.has_key('TARGET_PRODUCT') and
env.has_key('TARGET_BUILD_VARIANT')):
target = '%s-%s' % (env['TARGET_PRODUCT'],
env['TARGET_BUILD_VARIANT'])
[success, manifest_str] = server.GetApprovedManifest(branch, target)
if opt.manifest_name:
self.manifest.Override(opt.manifest_name)
manifest_name = opt.manifest_name
smart_sync_manifest_name = "smart_sync_override.xml"
smart_sync_manifest_path = os.path.join(
self.manifest.manifestProject.worktree, smart_sync_manifest_name)
if opt.smart_sync or opt.smart_tag:
if not self.manifest.manifest_server:
print('error: cannot smart sync: no manifest server defined in '
'manifest', file=sys.stderr)
sys.exit(1)
manifest_server = self.manifest.manifest_server
if not opt.quiet:
print('Using manifest server %s' % manifest_server)
if not '@' in manifest_server:
username = None
password = None
if opt.manifest_server_username and opt.manifest_server_password:
username = opt.manifest_server_username
password = opt.manifest_server_password
else:
[success, manifest_str] = server.GetApprovedManifest(branch)
try:
info = netrc.netrc()
except IOError:
print('.netrc file does not exist or could not be opened',
file=sys.stderr)
else:
try:
parse_result = urllib.parse.urlparse(manifest_server)
if parse_result.hostname:
username, _account, password = \
info.authenticators(parse_result.hostname)
except TypeError:
# TypeError is raised when the given hostname is not present
# in the .netrc file.
print('No credentials found for %s in .netrc'
% parse_result.hostname, file=sys.stderr)
except netrc.NetrcParseError as e:
print('Error parsing .netrc file: %s' % e, file=sys.stderr)
if (username and password):
manifest_server = manifest_server.replace('://', '://%s:%s@' %
(username, password),
1)
try:
server = xmlrpc.client.Server(manifest_server)
if opt.smart_sync:
p = self.manifest.manifestProject
b = p.GetBranch(p.CurrentBranch)
branch = b.merge
if branch.startswith(R_HEADS):
branch = branch[len(R_HEADS):]
env = os.environ.copy()
if 'SYNC_TARGET' in env:
target = env['SYNC_TARGET']
[success, manifest_str] = server.GetApprovedManifest(branch, target)
elif 'TARGET_PRODUCT' in env and 'TARGET_BUILD_VARIANT' in env:
target = '%s-%s' % (env['TARGET_PRODUCT'],
env['TARGET_BUILD_VARIANT'])
[success, manifest_str] = server.GetApprovedManifest(branch, target)
else:
[success, manifest_str] = server.GetApprovedManifest(branch)
else:
assert(opt.smart_tag)
[success, manifest_str] = server.GetManifest(opt.smart_tag)
if success:
manifest_name = "smart_sync_override.xml"
manifest_path = os.path.join(self.manifest.manifestProject.worktree,
manifest_name)
manifest_name = smart_sync_manifest_name
try:
f = open(manifest_path, 'w')
f = open(smart_sync_manifest_path, 'w')
try:
f.write(manifest_str)
finally:
f.close()
except IOError:
print >>sys.stderr, 'error: cannot write manifest to %s' % \
manifest_path
except IOError as e:
print('error: cannot write manifest to %s:\n%s'
% (smart_sync_manifest_path, e),
file=sys.stderr)
sys.exit(1)
self.manifest.Override(manifest_name)
self._ReloadManifest(manifest_name)
else:
print >>sys.stderr, 'error: %s' % manifest_str
print('error: manifest server RPC call failed: %s' %
manifest_str, file=sys.stderr)
sys.exit(1)
except socket.error:
print >>sys.stderr, 'error: cannot connect to manifest server %s' % (
self.manifest.manifest_server)
except (socket.error, IOError, xmlrpc.client.Fault) as e:
print('error: cannot connect to manifest server %s:\n%s'
% (self.manifest.manifest_server, e), file=sys.stderr)
sys.exit(1)
except xmlrpc.client.ProtocolError as e:
print('error: cannot connect to manifest server %s:\n%d %s'
% (self.manifest.manifest_server, e.errcode, e.errmsg),
file=sys.stderr)
sys.exit(1)
else: # Not smart sync or smart tag mode
if os.path.isfile(smart_sync_manifest_path):
try:
os.remove(smart_sync_manifest_path)
except OSError as e:
print('error: failed to remove existing smart sync override manifest: %s' %
e, file=sys.stderr)
rp = self.manifest.repoProject
rp.PreSync()
@ -367,25 +627,34 @@ uncommitted changes are present' % project.relpath
mp.PreSync()
if opt.repo_upgraded:
_PostRepoUpgrade(self.manifest)
_PostRepoUpgrade(self.manifest, quiet=opt.quiet)
if not opt.local_only:
mp.Sync_NetworkHalf(quiet=opt.quiet)
mp.Sync_NetworkHalf(quiet=opt.quiet,
current_branch_only=opt.current_branch_only,
no_tags=opt.no_tags,
optimized_fetch=opt.optimized_fetch)
if mp.HasChanges:
syncbuf = SyncBuffer(mp.config)
mp.Sync_LocalHalf(syncbuf)
if not syncbuf.Finish():
sys.exit(1)
self.manifest._Unload()
all = self.GetProjects(args, missing_ok=True)
self._ReloadManifest(manifest_name)
if opt.jobs is None:
self.jobs = self.manifest.default.sync_j
all_projects = self.GetProjects(args,
missing_ok=True,
submodules_ok=opt.fetch_submodules)
self._fetch_times = _FetchTimes(self.manifest)
if not opt.local_only:
to_fetch = []
now = time.time()
if (24 * 60 * 60) <= (now - rp.LastFetch):
if _ONE_DAY_S <= (now - rp.LastFetch):
to_fetch.append(rp)
to_fetch.extend(all)
to_fetch.extend(all_projects)
to_fetch.sort(key=self._fetch_times.Get, reverse=True)
fetched = self._Fetch(to_fetch, opt)
_PostRepoFetch(rp, opt.no_repo_verify)
@ -393,15 +662,28 @@ uncommitted changes are present' % project.relpath
# bail out now; the rest touches the working tree
return
self.manifest._Unload()
all = self.GetProjects(args, missing_ok=True)
# Iteratively fetch missing and/or nested unregistered submodules
previously_missing_set = set()
while True:
self._ReloadManifest(manifest_name)
all_projects = self.GetProjects(args,
missing_ok=True,
submodules_ok=opt.fetch_submodules)
missing = []
for project in all:
for project in all_projects:
if project.gitdir not in fetched:
missing.append(project)
self._Fetch(missing, opt)
if not missing:
break
# Stop us from non-stopped fetching actually-missing repos: If set of
# missing repos has not been changed from last fetch, we break.
missing_set = set(p.name for p in missing)
if previously_missing_set == missing_set:
break
previously_missing_set = missing_set
fetched.update(self._Fetch(missing, opt))
if self.manifest.IsMirror:
if self.manifest.IsMirror or self.manifest.IsArchive:
# bail out now, we have no working tree
return
@ -410,49 +692,53 @@ uncommitted changes are present' % project.relpath
syncbuf = SyncBuffer(mp.config,
detach_head = opt.detach_head)
pm = Progress('Syncing work tree', len(all))
for project in all:
pm = Progress('Syncing work tree', len(all_projects))
for project in all_projects:
pm.update()
if project.worktree:
project.Sync_LocalHalf(syncbuf)
pm.end()
print >>sys.stderr
print(file=sys.stderr)
if not syncbuf.Finish():
sys.exit(1)
# If there's a notice that's supposed to print at the end of the sync, print
# it now...
if self.manifest.notice:
print self.manifest.notice
print(self.manifest.notice)
def _PostRepoUpgrade(manifest):
for project in manifest.projects.values():
def _PostRepoUpgrade(manifest, quiet=False):
wrapper = Wrapper()
if wrapper.NeedSetupGnuPG():
wrapper.SetupGnuPG(quiet)
for project in manifest.projects:
if project.Exists:
project.PostRepoUpgrade()
def _PostRepoFetch(rp, no_repo_verify=False, verbose=False):
if rp.HasChanges:
print >>sys.stderr, 'info: A new version of repo is available'
print >>sys.stderr, ''
print('info: A new version of repo is available', file=sys.stderr)
print(file=sys.stderr)
if no_repo_verify or _VerifyTag(rp):
syncbuf = SyncBuffer(rp.config)
rp.Sync_LocalHalf(syncbuf)
if not syncbuf.Finish():
sys.exit(1)
print >>sys.stderr, 'info: Restarting repo with latest version'
print('info: Restarting repo with latest version', file=sys.stderr)
raise RepoChangedException(['--repo-upgraded'])
else:
print >>sys.stderr, 'warning: Skipped upgrade to unverified version'
print('warning: Skipped upgrade to unverified version', file=sys.stderr)
else:
if verbose:
print >>sys.stderr, 'repo version %s is current' % rp.work_git.describe(HEAD)
print('repo version %s is current' % rp.work_git.describe(HEAD),
file=sys.stderr)
def _VerifyTag(project):
gpg_dir = os.path.expanduser('~/.repoconfig/gnupg')
if not os.path.exists(gpg_dir):
print >>sys.stderr,\
"""warning: GnuPG was not available during last "repo init"
warning: Cannot automatically authenticate repo."""
print('warning: GnuPG was not available during last "repo init"\n'
'warning: Cannot automatically authenticate repo."""',
file=sys.stderr)
return True
try:
@ -466,10 +752,9 @@ warning: Cannot automatically authenticate repo."""
if rev.startswith(R_HEADS):
rev = rev[len(R_HEADS):]
print >>sys.stderr
print >>sys.stderr,\
"warning: project '%s' branch '%s' is not signed" \
% (project.name, rev)
print(file=sys.stderr)
print("warning: project '%s' branch '%s' is not signed"
% (project.name, rev), file=sys.stderr)
return False
env = os.environ.copy()
@ -488,9 +773,67 @@ warning: Cannot automatically authenticate repo."""
proc.stderr.close()
if proc.wait() != 0:
print >>sys.stderr
print >>sys.stderr, out
print >>sys.stderr, err
print >>sys.stderr
print(file=sys.stderr)
print(out, file=sys.stderr)
print(err, file=sys.stderr)
print(file=sys.stderr)
return False
return True
class _FetchTimes(object):
_ALPHA = 0.5
def __init__(self, manifest):
self._path = os.path.join(manifest.repodir, '.repo_fetchtimes.json')
self._times = None
self._seen = set()
def Get(self, project):
self._Load()
return self._times.get(project.name, _ONE_DAY_S)
def Set(self, project, t):
self._Load()
name = project.name
old = self._times.get(name, t)
self._seen.add(name)
a = self._ALPHA
self._times[name] = (a*t) + ((1-a) * old)
def _Load(self):
if self._times is None:
try:
f = open(self._path)
try:
self._times = json.load(f)
finally:
f.close()
except (IOError, ValueError):
try:
os.remove(self._path)
except OSError:
pass
self._times = {}
def Save(self):
if self._times is None:
return
to_delete = []
for name in self._times:
if name not in self._seen:
to_delete.append(name)
for name in to_delete:
del self._times[name]
try:
f = open(self._path, 'w')
try:
json.dump(self._times, f, indent=2)
finally:
f.close()
except (IOError, TypeError):
try:
os.remove(self._path)
except OSError:
pass

View File

@ -13,6 +13,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import print_function
import copy
import re
import sys
@ -20,34 +21,45 @@ import sys
from command import InteractiveCommand
from editor import Editor
from error import HookError, UploadError
from git_command import GitCommand
from project import RepoHook
from pyversion import is_python3
# pylint:disable=W0622
if not is_python3():
input = raw_input
else:
unicode = str
# pylint:enable=W0622
UNUSUAL_COMMIT_THRESHOLD = 5
def _ConfirmManyUploads(multiple_branches=False):
if multiple_branches:
print "ATTENTION: One or more branches has an unusually high number of commits."
print('ATTENTION: One or more branches has an unusually high number '
'of commits.')
else:
print "ATTENTION: You are uploading an unusually high number of commits."
print "YOU PROBABLY DO NOT MEAN TO DO THIS. (Did you rebase across branches?)"
answer = raw_input("If you are sure you intend to do this, type 'yes': ").strip()
print('ATTENTION: You are uploading an unusually high number of commits.')
print('YOU PROBABLY DO NOT MEAN TO DO THIS. (Did you rebase across '
'branches?)')
answer = input("If you are sure you intend to do this, type 'yes': ").strip()
return answer == "yes"
def _die(fmt, *args):
msg = fmt % args
print >>sys.stderr, 'error: %s' % msg
print('error: %s' % msg, file=sys.stderr)
sys.exit(1)
def _SplitEmails(values):
result = []
for str in values:
result.extend([s.strip() for s in str.split(',')])
for value in values:
result.extend([s.strip() for s in value.split(',')])
return result
class Upload(InteractiveCommand):
common = True
helpSummary = "Upload changes for code review"
helpUsage="""
helpUsage = """
%prog [--re --cc] [<project>]...
"""
helpDescription = """
@ -73,12 +85,17 @@ Configuration
review.URL.autoupload:
To disable the "Upload ... (y/n)?" prompt, you can set a per-project
To disable the "Upload ... (y/N)?" prompt, you can set a per-project
or global Git configuration option. If review.URL.autoupload is set
to "true" then repo will assume you always answer "y" at the prompt,
and will not prompt you further. If it is set to "false" then repo
will assume you always answer "n", and will abort.
review.URL.autoreviewer:
To automatically append a user or mailing list to reviews, you can set
a per-project or global Git option to do so.
review.URL.autocopy:
To automatically copy a user or mailing list to all uploaded reviews,
@ -103,6 +120,14 @@ or in the .git/config within the project. For example:
autoupload = true
autocopy = johndoe@company.com,my-team-alias@company.com
review.URL.uploadtopic:
To add a topic branch whenever uploading a commit, you can set a
per-project or global Git option to do so. If review.URL.uploadtopic
is set to "true" then repo will assume you always want the equivalent
of the -t option to the repo command. If unset or set to "false" then
repo will make use of only the command line option.
References
----------
@ -120,6 +145,19 @@ Gerrit Code Review: http://code.google.com/p/gerrit/
p.add_option('--cc',
type='string', action='append', dest='cc',
help='Also send email to these email addresses.')
p.add_option('--br',
type='string', action='store', dest='branch',
help='Branch to upload.')
p.add_option('--cbr', '--current-branch',
dest='current_branch', action='store_true',
help='Upload current git branch.')
p.add_option('-d', '--draft',
action='store_true', dest='draft', default=False,
help='If specified, upload as a draft.')
p.add_option('-D', '--destination', '--dest',
type='string', action='store', dest='dest_branch',
metavar='BRANCH',
help='Submit for review on this target branch.')
# Options relating to upload hook. Note that verify and no-verify are NOT
# opposites of each other, which is why they store to different locations.
@ -157,20 +195,21 @@ Gerrit Code Review: http://code.google.com/p/gerrit/
if answer is None:
date = branch.date
list = branch.commits
commit_list = branch.commits
print 'Upload project %s/:' % project.relpath
print ' branch %s (%2d commit%s, %s):' % (
destination = opt.dest_branch or project.dest_branch or project.revisionExpr
print('Upload project %s/ to remote branch %s:' % (project.relpath, destination))
print(' branch %s (%2d commit%s, %s):' % (
name,
len(list),
len(list) != 1 and 's' or '',
date)
for commit in list:
print ' %s' % commit
len(commit_list),
len(commit_list) != 1 and 's' or '',
date))
for commit in commit_list:
print(' %s' % commit)
sys.stdout.write('to %s (y/n)? ' % remote.review)
answer = sys.stdin.readline().strip()
answer = answer in ('y', 'Y', 'yes', '1', 'true', 't')
sys.stdout.write('to %s (y/N)? ' % remote.review)
answer = sys.stdin.readline().strip().lower()
answer = answer in ('y', 'yes', '1', 'true', 't')
if answer:
if len(branch.commits) > UNUSUAL_COMMIT_THRESHOLD:
@ -193,18 +232,22 @@ Gerrit Code Review: http://code.google.com/p/gerrit/
b = {}
for branch in avail:
if branch is None:
continue
name = branch.name
date = branch.date
list = branch.commits
commit_list = branch.commits
if b:
script.append('#')
script.append('# branch %s (%2d commit%s, %s):' % (
destination = opt.dest_branch or project.dest_branch or project.revisionExpr
script.append('# branch %s (%2d commit%s, %s) to remote branch %s:' % (
name,
len(list),
len(list) != 1 and 's' or '',
date))
for commit in list:
len(commit_list),
len(commit_list) != 1 and 's' or '',
date,
destination))
for commit in commit_list:
script.append('# %s' % commit)
b[name] = branch
@ -212,6 +255,11 @@ Gerrit Code Review: http://code.google.com/p/gerrit/
branches[project.name] = b
script.append('')
script = [ x.encode('utf-8')
if issubclass(type(x), unicode)
else x
for x in script ]
script = Editor.EditString("\n".join(script)).split("\n")
project_re = re.compile(r'^#?\s*project\s*([^\s]+)/:$')
@ -252,14 +300,20 @@ Gerrit Code Review: http://code.google.com/p/gerrit/
self._UploadAndReport(opt, todo, people)
def _AppendAutoCcList(self, branch, people):
def _AppendAutoList(self, branch, people):
"""
Appends the list of reviewers in the git project's config.
Appends the list of users in the CC list in the git project's config if a
non-empty reviewer list was found.
"""
name = branch.name
project = branch.project
key = 'review.%s.autoreviewer' % project.GetBranch(name).remote.review
raw_list = project.config.GetString(key)
if not raw_list is None:
people[0].extend([entry.strip() for entry in raw_list.split(',')])
key = 'review.%s.autocopy' % project.GetBranch(name).remote.review
raw_list = project.config.GetString(key)
if not raw_list is None and len(people[0]) > 0:
@ -274,7 +328,7 @@ Gerrit Code Review: http://code.google.com/p/gerrit/
try:
# refs/changes/XYZ/N --> XYZ
return refs.get(last_pub).split('/')[-2]
except:
except (AttributeError, IndexError):
return ""
def _UploadAndReport(self, opt, todo, original_people):
@ -282,32 +336,56 @@ Gerrit Code Review: http://code.google.com/p/gerrit/
for branch in todo:
try:
people = copy.deepcopy(original_people)
self._AppendAutoCcList(branch, people)
self._AppendAutoList(branch, people)
# Check if there are local changes that may have been forgotten
if branch.project.HasChanges():
key = 'review.%s.autoupload' % branch.project.remote.review
answer = branch.project.config.GetBoolean(key)
changes = branch.project.UncommitedFiles()
if changes:
key = 'review.%s.autoupload' % branch.project.remote.review
answer = branch.project.config.GetBoolean(key)
# if they want to auto upload, let's not ask because it could be automated
if answer is None:
sys.stdout.write('Uncommitted changes in ' + branch.project.name + ' (did you forget to amend?). Continue uploading? (y/n) ')
a = sys.stdin.readline().strip().lower()
if a not in ('y', 'yes', 't', 'true', 'on'):
print >>sys.stderr, "skipping upload"
branch.uploaded = False
branch.error = 'User aborted'
continue
# if they want to auto upload, let's not ask because it could be automated
if answer is None:
sys.stdout.write('Uncommitted changes in ' + branch.project.name)
sys.stdout.write(' (did you forget to amend?):\n')
sys.stdout.write('\n'.join(changes) + '\n')
sys.stdout.write('Continue uploading? (y/N) ')
a = sys.stdin.readline().strip().lower()
if a not in ('y', 'yes', 't', 'true', 'on'):
print("skipping upload", file=sys.stderr)
branch.uploaded = False
branch.error = 'User aborted'
continue
branch.UploadForReview(people, auto_topic=opt.auto_topic)
# Check if topic branches should be sent to the server during upload
if opt.auto_topic is not True:
key = 'review.%s.uploadtopic' % branch.project.remote.review
opt.auto_topic = branch.project.config.GetBoolean(key)
destination = opt.dest_branch or branch.project.dest_branch
# Make sure our local branch is not setup to track a different remote branch
merge_branch = self._GetMergeBranch(branch.project)
if destination:
full_dest = 'refs/heads/%s' % destination
if not opt.dest_branch and merge_branch and merge_branch != full_dest:
print('merge branch %s does not match destination branch %s'
% (merge_branch, full_dest))
print('skipping upload.')
print('Please use `--destination %s` if this is intentional'
% destination)
branch.uploaded = False
continue
branch.UploadForReview(people, auto_topic=opt.auto_topic, draft=opt.draft, dest_branch=destination)
branch.uploaded = True
except UploadError, e:
except UploadError as e:
branch.error = e
branch.uploaded = False
have_errors = True
print >>sys.stderr, ''
print >>sys.stderr, '----------------------------------------------------------------------'
print(file=sys.stderr)
print('----------------------------------------------------------------------', file=sys.stderr)
if have_errors:
for branch in todo:
@ -316,29 +394,63 @@ Gerrit Code Review: http://code.google.com/p/gerrit/
fmt = ' (%s)'
else:
fmt = '\n (%s)'
print >>sys.stderr, ('[FAILED] %-15s %-15s' + fmt) % (
print(('[FAILED] %-15s %-15s' + fmt) % (
branch.project.relpath + '/', \
branch.name, \
str(branch.error))
print >>sys.stderr, ''
str(branch.error)),
file=sys.stderr)
print()
for branch in todo:
if branch.uploaded:
print >>sys.stderr, '[OK ] %-15s %s' % (
branch.project.relpath + '/',
branch.name)
if branch.uploaded:
print('[OK ] %-15s %s' % (
branch.project.relpath + '/',
branch.name),
file=sys.stderr)
if have_errors:
sys.exit(1)
def _GetMergeBranch(self, project):
p = GitCommand(project,
['rev-parse', '--abbrev-ref', 'HEAD'],
capture_stdout = True,
capture_stderr = True)
p.Wait()
local_branch = p.stdout.strip()
p = GitCommand(project,
['config', '--get', 'branch.%s.merge' % local_branch],
capture_stdout = True,
capture_stderr = True)
p.Wait()
merge_branch = p.stdout.strip()
return merge_branch
def Execute(self, opt, args):
project_list = self.GetProjects(args)
pending = []
reviewers = []
cc = []
branch = None
if opt.branch:
branch = opt.branch
for project in project_list:
avail = project.GetUploadableBranches()
if opt.current_branch:
cbr = project.CurrentBranch
up_branch = project.GetUploadableBranch(cbr)
if up_branch:
avail = [up_branch]
else:
avail = None
print('ERROR: Current branch (%s) not uploadable. '
'You may be able to type '
'"git branch --set-upstream-to m/master" to fix '
'your branch.' % str(cbr),
file=sys.stderr)
else:
avail = project.GetUploadableBranches(branch)
if avail:
pending.append((project, avail))
@ -346,20 +458,22 @@ Gerrit Code Review: http://code.google.com/p/gerrit/
hook = RepoHook('pre-upload', self.manifest.repo_hooks_project,
self.manifest.topdir, abort_if_user_denies=True)
pending_proj_names = [project.name for (project, avail) in pending]
pending_worktrees = [project.worktree for (project, avail) in pending]
try:
hook.Run(opt.allow_all_hooks, project_list=pending_proj_names)
except HookError, e:
print >>sys.stderr, "ERROR: %s" % str(e)
hook.Run(opt.allow_all_hooks, project_list=pending_proj_names,
worktree_list=pending_worktrees)
except HookError as e:
print("ERROR: %s" % str(e), file=sys.stderr)
return
if opt.reviewers:
reviewers = _SplitEmails(opt.reviewers)
if opt.cc:
cc = _SplitEmails(opt.cc)
people = (reviewers,cc)
people = (reviewers, cc)
if not pending:
print >>sys.stdout, "no branches ready for upload"
print("no branches ready for upload", file=sys.stderr)
elif len(pending) == 1 and len(pending[0][1]) == 1:
self._SingleBranch(opt, pending[0][1][0], people)
else:

View File

@ -13,12 +13,16 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import print_function
import sys
from command import Command, MirrorSafeCommand
from git_command import git
from project import HEAD
from git_refs import HEAD
class Version(Command, MirrorSafeCommand):
wrapper_version = None
wrapper_path = None
common = False
helpSummary = "Display the version of repo"
helpUsage = """
@ -29,7 +33,12 @@ class Version(Command, MirrorSafeCommand):
rp = self.manifest.repoProject
rem = rp.GetRemote(rp.remote.name)
print 'repo version %s' % rp.work_git.describe(HEAD)
print ' (from %s)' % rem.url
print git.version().strip()
print 'Python %s' % sys.version
print('repo version %s' % rp.work_git.describe(HEAD))
print(' (from %s)' % rem.url)
if Version.wrapper_path is not None:
print('repo launcher version %s' % Version.wrapper_version)
print(' (from %s)' % Version.wrapper_path)
print(git.version().strip())
print('Python %s' % sys.version)

View File

@ -4,49 +4,49 @@ import unittest
import git_config
def fixture(*paths):
"""Return a path relative to test/fixtures.
"""
return os.path.join(os.path.dirname(__file__), 'fixtures', *paths)
"""Return a path relative to test/fixtures.
"""
return os.path.join(os.path.dirname(__file__), 'fixtures', *paths)
class GitConfigUnitTest(unittest.TestCase):
"""Tests the GitConfig class.
"""Tests the GitConfig class.
"""
def setUp(self):
"""Create a GitConfig object using the test.gitconfig fixture.
"""
def setUp(self):
"""Create a GitConfig object using the test.gitconfig fixture.
"""
config_fixture = fixture('test.gitconfig')
self.config = git_config.GitConfig(config_fixture)
config_fixture = fixture('test.gitconfig')
self.config = git_config.GitConfig(config_fixture)
def test_GetString_with_empty_config_values(self):
"""
Test config entries with no value.
def test_GetString_with_empty_config_values(self):
"""
Test config entries with no value.
[section]
empty
[section]
empty
"""
val = self.config.GetString('section.empty')
self.assertEqual(val, None)
"""
val = self.config.GetString('section.empty')
self.assertEqual(val, None)
def test_GetString_with_true_value(self):
"""
Test config entries with a string value.
def test_GetString_with_true_value(self):
"""
Test config entries with a string value.
[section]
nonempty = true
[section]
nonempty = true
"""
val = self.config.GetString('section.nonempty')
self.assertEqual(val, 'true')
"""
val = self.config.GetString('section.nonempty')
self.assertEqual(val, 'true')
def test_GetString_from_missing_file(self):
"""
Test missing config file
"""
config_fixture = fixture('not.present.gitconfig')
config = git_config.GitConfig(config_fixture)
val = config.GetString('empty')
self.assertEqual(val, None)
def test_GetString_from_missing_file(self):
"""
Test missing config file
"""
config_fixture = fixture('not.present.gitconfig')
config = git_config.GitConfig(config_fixture)
val = config.GetString('empty')
self.assertEqual(val, None)
if __name__ == '__main__':
unittest.main()
unittest.main()

View File

@ -13,6 +13,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import print_function
import sys
import os
REPO_TRACE = 'REPO_TRACE'
@ -31,4 +32,4 @@ def SetTrace():
def Trace(fmt, *args):
if IsTrace():
print >>sys.stderr, fmt % args
print(fmt % args, file=sys.stderr)

30
wrapper.py Normal file
View File

@ -0,0 +1,30 @@
#!/usr/bin/env python
#
# Copyright (C) 2014 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import print_function
import imp
import os
def WrapperPath():
return os.path.join(os.path.dirname(__file__), 'repo')
_wrapper_module = None
def Wrapper():
global _wrapper_module
if not _wrapper_module:
_wrapper_module = imp.load_source('wrapper', WrapperPath())
return _wrapper_module