Compare commits

...

210 Commits

Author SHA1 Message Date
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
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
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
48 changed files with 2660 additions and 1084 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 *.pyc
.repopickle_* .repopickle_*
/repoc

View File

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

View File

@ -3,7 +3,7 @@
<pydev_project> <pydev_project>
<pydev_pathproperty name="org.python.pydev.PROJECT_SOURCE_PATH"> <pydev_pathproperty name="org.python.pydev.PROJECT_SOURCE_PATH">
<path>/repo</path> <path>/git-repo</path>
</pydev_pathproperty> </pydev_pathproperty>
<pydev_property name="org.python.pydev.PYTHON_PROJECT_VERSION">python 2.6</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_property name="org.python.pydev.PYTHON_PROJECT_INTERPRETER">Default</pydev_property>

View File

@ -53,7 +53,7 @@ load-plugins=
enable=RP0004 enable=RP0004
# Disable the message(s) with the given id(s). # Disable the message(s) with the given id(s).
disable=R0903,R0912,R0913,R0914,R0915,W0141,C0111,C0103,C0323,C0322,C0324,W0603,W0703,R0911,C0301,C0302,R0902,R0904,W0142,W0212,E1101,E1103,R0201,W0201,W0122,W0232,W0311,RP0001,RP0003,RP0101,RP0002,RP0401,RP0701,RP0801 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] [REPORTS]

View File

@ -36,50 +36,51 @@ ATTRS = {None :-1,
'blink' : 5, 'blink' : 5,
'reverse': 7} 'reverse': 7}
RESET = "\033[m" RESET = "\033[m" # pylint: disable=W1401
# backslash is not anomalous
def is_color(s): def is_color(s):
return s in COLORS return s in COLORS
def is_attr(s): def is_attr(s):
return s in ATTRS return s in ATTRS
def _Color(fg = None, bg = None, attr = None): def _Color(fg = None, bg = None, attr = None):
fg = COLORS[fg] fg = COLORS[fg]
bg = COLORS[bg] bg = COLORS[bg]
attr = ATTRS[attr] attr = ATTRS[attr]
if attr >= 0 or fg >= 0 or bg >= 0: if attr >= 0 or fg >= 0 or bg >= 0:
need_sep = False need_sep = False
code = "\033[" code = "\033[" #pylint: disable=W1401
if attr >= 0: if attr >= 0:
code += chr(ord('0') + attr) code += chr(ord('0') + attr)
need_sep = True need_sep = True
if fg >= 0: if fg >= 0:
if need_sep: if need_sep:
code += ';' code += ';'
need_sep = True need_sep = True
if fg < 8: if fg < 8:
code += '3%c' % (ord('0') + fg) code += '3%c' % (ord('0') + fg)
else: else:
code += '38;5;%d' % fg code += '38;5;%d' % fg
if bg >= 0: if bg >= 0:
if need_sep: if need_sep:
code += ';' code += ';'
need_sep = True need_sep = True
if bg < 8: if bg < 8:
code += '4%c' % (ord('0') + bg) code += '4%c' % (ord('0') + bg)
else: else:
code += '48;5;%d' % bg code += '48;5;%d' % bg
code += 'm' code += 'm'
else: else:
code = '' code = ''
return code return code
class Coloring(object): class Coloring(object):
@ -125,6 +126,13 @@ class Coloring(object):
s._out.write(c(fmt, *args)) s._out.write(c(fmt, *args))
return f 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): def colorer(self, opt=None, fg=None, bg=None, attr=None):
if self._on: if self._on:
c = self._parse(opt, fg, bg, attr) c = self._parse(opt, fg, bg, attr)
@ -137,6 +145,17 @@ class Coloring(object):
return fmt % args return fmt % args
return f 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): def _parse(self, opt, fg, bg, attr):
if not opt: if not opt:
return _Color(fg, bg, attr) return _Color(fg, bg, attr)

View File

@ -22,6 +22,7 @@ import sys
from error import NoSuchProjectError from error import NoSuchProjectError
from error import InvalidProjectGroupsError from error import InvalidProjectGroupsError
class Command(object): class Command(object):
"""Base class for any command line action in repo. """Base class for any command line action in repo.
""" """
@ -33,6 +34,27 @@ class Command(object):
def WantPager(self, opt): def WantPager(self, opt):
return False 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 @property
def OptionParser(self): def OptionParser(self):
if self._optparse is None: if self._optparse is None:
@ -49,6 +71,24 @@ class Command(object):
"""Initialize the option parser. """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): def Usage(self):
"""Display usage and terminate. """Display usage and terminate.
""" """
@ -86,46 +126,44 @@ class Command(object):
pass pass
return project return project
def GetProjects(self, args, missing_ok=False): def GetProjects(self, args, missing_ok=False, submodules_ok=False):
"""A list of projects that match the arguments. """A list of projects that match the arguments.
""" """
all_projects = self.manifest.projects all_projects_list = self.manifest.projects
result = [] result = []
mp = self.manifest.manifestProject mp = self.manifest.manifestProject
groups = mp.config.GetString('manifest.groups') groups = mp.config.GetString('manifest.groups')
if not groups: if not groups:
groups = 'all,-notdefault,platform-' + platform.system().lower() groups = 'default,platform-' + platform.system().lower()
groups = [x for x in re.split('[,\s]+', groups) if x] groups = [x for x in re.split(r'[,\s]+', groups) if x]
if not args: if not args:
all_projects_list = all_projects.values() derived_projects = {}
derived_projects = []
for project in all_projects_list: for project in all_projects_list:
if project.Registered: if submodules_ok or project.sync_s:
# Do not search registered subproject for derived projects derived_projects.update((p.name, p)
# since its parent has been searched already for p in project.GetDerivedSubprojects())
continue all_projects_list.extend(derived_projects.values())
derived_projects.extend(project.GetDerivedSubprojects())
all_projects_list.extend(derived_projects)
for project in all_projects_list: for project in all_projects_list:
if ((missing_ok or project.Exists) and if ((missing_ok or project.Exists) and
project.MatchesGroups(groups)): project.MatchesGroups(groups)):
result.append(project) result.append(project)
else: else:
self._ResetPathToProjectMap(all_projects.values()) self._ResetPathToProjectMap(all_projects_list)
for arg in args: for arg in args:
project = all_projects.get(arg) projects = self.manifest.GetProjectsWithName(arg)
if not project: if not projects:
path = os.path.abspath(arg).replace('\\', '/') path = os.path.abspath(arg).replace('\\', '/')
project = self._GetProjectByPath(path) project = self._GetProjectByPath(path)
# If it's not a derived project, update path->project mapping and # If it's not a derived project, update path->project mapping and
# search again, as arg might actually point to a derived subproject. # search again, as arg might actually point to a derived subproject.
if project and not project.Derived: if (project and not project.Derived and
(submodules_ok or project.sync_s)):
search_again = False search_again = False
for subproject in project.GetDerivedSubprojects(): for subproject in project.GetDerivedSubprojects():
self._UpdatePathToProjectMap(subproject) self._UpdatePathToProjectMap(subproject)
@ -133,20 +171,36 @@ class Command(object):
if search_again: if search_again:
project = self._GetProjectByPath(path) or project project = self._GetProjectByPath(path) or project
if not project: if project:
raise NoSuchProjectError(arg) projects = [project]
if not missing_ok and not project.Exists:
raise NoSuchProjectError(arg)
if not project.MatchesGroups(groups):
raise InvalidProjectGroupsError(arg)
result.append(project) if not projects:
raise NoSuchProjectError(arg)
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): def _getpath(x):
return x.relpath return x.relpath
result.sort(key=_getpath) result.sort(key=_getpath)
return result 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: disable=W0223
# Pylint warns that the `InteractiveCommand` and `PagedCommand` classes do not # Pylint warns that the `InteractiveCommand` and `PagedCommand` classes do not
# override method `Execute` which is abstract in `Command`. Since that method # override method `Execute` which is abstract in `Command`. Since that method

View File

@ -27,38 +27,45 @@ following DTD:
remove-project*, remove-project*,
project*, project*,
repo-hooks?)> repo-hooks?)>
<!ELEMENT notice (#PCDATA)> <!ELEMENT notice (#PCDATA)>
<!ELEMENT remote (EMPTY)> <!ELEMENT remote (EMPTY)>
<!ATTLIST remote name ID #REQUIRED> <!ATTLIST remote name ID #REQUIRED>
<!ATTLIST remote alias CDATA #IMPLIED> <!ATTLIST remote alias CDATA #IMPLIED>
<!ATTLIST remote fetch CDATA #REQUIRED> <!ATTLIST remote fetch CDATA #REQUIRED>
<!ATTLIST remote review CDATA #IMPLIED> <!ATTLIST remote review CDATA #IMPLIED>
<!ELEMENT default (EMPTY)> <!ELEMENT default (EMPTY)>
<!ATTLIST default remote IDREF #IMPLIED> <!ATTLIST default remote IDREF #IMPLIED>
<!ATTLIST default revision CDATA #IMPLIED> <!ATTLIST default revision CDATA #IMPLIED>
<!ATTLIST default sync-j CDATA #IMPLIED> <!ATTLIST default dest-branch CDATA #IMPLIED>
<!ATTLIST default sync-c CDATA #IMPLIED> <!ATTLIST default sync-j CDATA #IMPLIED>
<!ATTLIST default sync-c CDATA #IMPLIED>
<!ATTLIST default sync-s CDATA #IMPLIED>
<!ELEMENT manifest-server (EMPTY)> <!ELEMENT manifest-server (EMPTY)>
<!ATTLIST url CDATA #REQUIRED> <!ATTLIST url CDATA #REQUIRED>
<!ELEMENT project (annotation?, <!ELEMENT project (annotation*,
project*)> project*)>
<!ATTLIST project name CDATA #REQUIRED> <!ATTLIST project name CDATA #REQUIRED>
<!ATTLIST project path CDATA #IMPLIED> <!ATTLIST project path CDATA #IMPLIED>
<!ATTLIST project remote IDREF #IMPLIED> <!ATTLIST project remote IDREF #IMPLIED>
<!ATTLIST project revision CDATA #IMPLIED> <!ATTLIST project revision CDATA #IMPLIED>
<!ATTLIST project groups CDATA #IMPLIED> <!ATTLIST project dest-branch CDATA #IMPLIED>
<!ATTLIST project sync-c 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)> <!ELEMENT annotation (EMPTY)>
<!ATTLIST annotation name CDATA #REQUIRED> <!ATTLIST annotation name CDATA #REQUIRED>
<!ATTLIST annotation value CDATA #REQUIRED> <!ATTLIST annotation value CDATA #REQUIRED>
<!ATTLIST annotation keep CDATA "true"> <!ATTLIST annotation keep CDATA "true">
<!ELEMENT remove-project (EMPTY)> <!ELEMENT remove-project (EMPTY)>
<!ATTLIST remove-project name CDATA #REQUIRED> <!ATTLIST remove-project name CDATA #REQUIRED>
@ -120,6 +127,20 @@ Attribute `revision`: Name of a Git branch (e.g. `master` or
`refs/heads/master`). Project elements lacking their own `refs/heads/master`). Project elements lacking their own
revision attribute will use this revision. 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 Element manifest-server
----------------------- -----------------------
@ -189,6 +210,11 @@ Tags and/or explicit SHA-1s should work in theory, but have not
been extensively tested. If not supplied the revision given by been extensively tested. If not supplied the revision given by
the default element is used. 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, Attribute `groups`: List of groups to which this project belongs,
whitespace or comma separated. All projects belong to the group whitespace or comma separated. All projects belong to the group
"all", and each project automatically belongs to a group of "all", and each project automatically belongs to a group of
@ -200,6 +226,26 @@ group "notdefault", it will not be automatically downloaded by repo.
If the project has a parent element, the `name` and `path` here If the project has a parent element, the `name` and `path` here
are the prefixed ones. 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 branch 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 annotation Element annotation
------------------ ------------------
@ -218,7 +264,7 @@ Deletes the named project from the internal manifest table, possibly
allowing a subsequent project element in the same manifest file to allowing a subsequent project element in the same manifest file to
replace the project with a different source. 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 the user can remove a project, and possibly replace it with their
own definition. own definition.
@ -227,21 +273,25 @@ Element include
This element provides the capability of including another manifest This element provides the capability of including another manifest
file into the originating manifest. Normal rules apply for the file into the originating manifest. Normal rules apply for the
target manifest to include- it must be a usable manifest on it's own. target manifest to include - it must be a usable manifest on its own.
Attribute `name`; the manifest to include, specified relative to Attribute `name`: the manifest to include, specified relative to
the manifest repositories root. the manifest repository's root.
Local Manifest Local Manifests
============== ===============
Additional remotes and projects may be added through a local Additional remotes and projects may be added through local manifest
manifest, stored in `$TOP_DIR/.repo/local_manifest.xml`. files stored in `$TOP_DIR/.repo/local_manifests/*.xml`.
For example: 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"?> <?xml version="1.0" encoding="UTF-8"?>
<manifest> <manifest>
<project path="manifest" <project path="manifest"
@ -250,6 +300,17 @@ For example:
name="platform/manifest" /> name="platform/manifest" />
</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 invocation, instructing repo to automatically download and manage
these extra projects. 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 # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from __future__ import print_function
import os import os
import re import re
import sys import sys
@ -53,10 +54,10 @@ class Editor(object):
return e return e
if os.getenv('TERM') == 'dumb': if os.getenv('TERM') == 'dumb':
print >>sys.stderr,\ print(
"""No editor specified in GIT_EDITOR, core.editor, VISUAL or EDITOR. """No editor specified in GIT_EDITOR, core.editor, VISUAL or EDITOR.
Tried to fall back to vi but terminal is dumb. Please configure at 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) sys.exit(1)
return 'vi' return 'vi'
@ -67,7 +68,7 @@ least one of these before using this command."""
Args: Args:
data : the text to edit data : the text to edit
Returns: Returns:
new value of edited text; None if editing did not succeed new value of edited text; None if editing did not succeed
""" """

View File

@ -21,6 +21,17 @@ class ManifestInvalidRevisionError(Exception):
"""The revision value in a project is incorrect. """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): class EditorError(Exception):
"""Unspecified error from the user's text editor. """Unspecified error from the user's text editor.
""" """

View File

@ -13,6 +13,7 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from __future__ import print_function
import os import os
import sys import sys
import subprocess import subprocess
@ -20,6 +21,7 @@ import tempfile
from signal import SIGTERM from signal import SIGTERM
from error import GitError from error import GitError
from trace import REPO_TRACE, IsTrace, Trace from trace import REPO_TRACE, IsTrace, Trace
from wrapper import Wrapper
GIT = 'git' GIT = 'git'
MIN_GIT_VERSION = (1, 5, 4) MIN_GIT_VERSION = (1, 5, 4)
@ -83,16 +85,11 @@ class _GitCall(object):
def version_tuple(self): def version_tuple(self):
global _git_version global _git_version
if _git_version is None: if _git_version is None:
ver_str = git.version() ver_str = git.version().decode('utf-8')
if ver_str.startswith('git version '): _git_version = Wrapper().ParseGitVersion(ver_str)
_git_version = tuple( if _git_version is None:
map(lambda x: int(x), print('fatal: "%s" unsupported' % ver_str, file=sys.stderr)
ver_str[len('git version '):].strip().split('-')[0].split('.')[0:3]
))
else:
print >>sys.stderr, 'fatal: "%s" unsupported' % ver_str
sys.exit(1) sys.exit(1)
return _git_version return _git_version
@ -110,8 +107,8 @@ def git_require(min_version, fail=False):
if min_version <= git_version: if min_version <= git_version:
return True return True
if fail: if fail:
need = '.'.join(map(lambda x: str(x), min_version)) need = '.'.join(map(str, min_version))
print >>sys.stderr, 'fatal: git %s or later required' % need print('fatal: git %s or later required' % need, file=sys.stderr)
sys.exit(1) sys.exit(1)
return False return False
@ -132,15 +129,15 @@ class GitCommand(object):
gitdir = None): gitdir = None):
env = os.environ.copy() env = os.environ.copy()
for e in [REPO_TRACE, for key in [REPO_TRACE,
GIT_DIR, GIT_DIR,
'GIT_ALTERNATE_OBJECT_DIRECTORIES', 'GIT_ALTERNATE_OBJECT_DIRECTORIES',
'GIT_OBJECT_DIRECTORY', 'GIT_OBJECT_DIRECTORY',
'GIT_WORK_TREE', 'GIT_WORK_TREE',
'GIT_GRAFT_FILE', 'GIT_GRAFT_FILE',
'GIT_INDEX_FILE']: 'GIT_INDEX_FILE']:
if e in env: if key in env:
del env[e] del env[key]
if disable_editor: if disable_editor:
_setenv(env, 'GIT_EDITOR', ':') _setenv(env, 'GIT_EDITOR', ':')

View File

@ -13,8 +13,10 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
import cPickle from __future__ import print_function
import os import os
import pickle
import re import re
import subprocess import subprocess
import sys import sys
@ -23,11 +25,25 @@ try:
except ImportError: except ImportError:
import dummy_threading as _threading import dummy_threading as _threading
import time 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 signal import SIGTERM
from error import GitError, UploadError from error import GitError, UploadError
from trace import Trace 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 GitCommand
from git_command import ssh_sock from git_command import ssh_sock
@ -35,7 +51,7 @@ from git_command import terminate_ssh_clients
R_HEADS = 'refs/heads/' R_HEADS = 'refs/heads/'
R_TAGS = 'refs/tags/' R_TAGS = 'refs/tags/'
ID_RE = re.compile('^[0-9a-f]{40}$') ID_RE = re.compile(r'^[0-9a-f]{40}$')
REVIEW_CACHE = dict() REVIEW_CACHE = dict()
@ -157,7 +173,7 @@ class GitConfig(object):
elif old != value: elif old != value:
self._cache[key] = list(value) self._cache[key] = list(value)
self._do('--replace-all', name, value[0]) 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]) self._do('--add', name, value[i])
elif len(old) != 1 or old[0] != value: elif len(old) != 1 or old[0] != value:
@ -250,7 +266,7 @@ class GitConfig(object):
Trace(': unpickle %s', self.file) Trace(': unpickle %s', self.file)
fd = open(self._pickle, 'rb') fd = open(self._pickle, 'rb')
try: try:
return cPickle.load(fd) return pickle.load(fd)
finally: finally:
fd.close() fd.close()
except EOFError: except EOFError:
@ -259,7 +275,7 @@ class GitConfig(object):
except IOError: except IOError:
os.remove(self._pickle) os.remove(self._pickle)
return None return None
except cPickle.PickleError: except pickle.PickleError:
os.remove(self._pickle) os.remove(self._pickle)
return None return None
@ -267,13 +283,13 @@ class GitConfig(object):
try: try:
fd = open(self._pickle, 'wb') fd = open(self._pickle, 'wb')
try: try:
cPickle.dump(cache, fd, cPickle.HIGHEST_PROTOCOL) pickle.dump(cache, fd, pickle.HIGHEST_PROTOCOL)
finally: finally:
fd.close() fd.close()
except IOError: except IOError:
if os.path.exists(self._pickle): if os.path.exists(self._pickle):
os.remove(self._pickle) os.remove(self._pickle)
except cPickle.PickleError: except pickle.PickleError:
if os.path.exists(self._pickle): if os.path.exists(self._pickle):
os.remove(self._pickle) os.remove(self._pickle)
@ -288,12 +304,13 @@ class GitConfig(object):
d = self._do('--null', '--list') d = self._do('--null', '--list')
if d is None: if d is None:
return c 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: if '\n' in line:
key, val = line.split('\n', 1) key, val = line.split('\n', 1)
else: else:
key = line key = line
val = None val = None
if key in c: if key in c:
c[key].append(val) c[key].append(val)
@ -418,7 +435,7 @@ def _open_ssh(host, port=None):
'-o','ControlPath %s' % ssh_sock(), '-o','ControlPath %s' % ssh_sock(),
host] host]
if port is not None: 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. # 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 # ...but before actually starting a master, we'll double-check. This can
@ -451,9 +468,8 @@ def _open_ssh(host, port=None):
p = subprocess.Popen(command) p = subprocess.Popen(command)
except Exception as e: except Exception as e:
_ssh_master = False _ssh_master = False
print >>sys.stderr, \ print('\nwarn: cannot enable ssh control master for %s:%s\n%s'
'\nwarn: cannot enable ssh control master for %s:%s\n%s' \ % (host,port, str(e)), file=sys.stderr)
% (host,port, str(e))
return False return False
_master_processes.append(p) _master_processes.append(p)
@ -525,8 +541,8 @@ class Remote(object):
self.url = self._Get('url') self.url = self._Get('url')
self.review = self._Get('review') self.review = self._Get('review')
self.projectname = self._Get('projectname') self.projectname = self._Get('projectname')
self.fetch = map(lambda x: RefSpec.FromString(x), self.fetch = list(map(RefSpec.FromString,
self._Get('fetch', all_keys=True)) self._Get('fetch', all_keys=True)))
self._review_url = None self._review_url = None
def _InsteadOf(self): def _InsteadOf(self):
@ -560,7 +576,7 @@ class Remote(object):
return None return None
u = self.review u = self.review
if not u.startswith('http:') and not u.startswith('https:'): if u.split(':')[0] not in ('http', 'https', 'sso'):
u = 'http://%s' % u u = 'http://%s' % u
if u.endswith('/Gerrit'): if u.endswith('/Gerrit'):
u = u[:len(u) - len('/Gerrit')] u = u[:len(u) - len('/Gerrit')]
@ -576,26 +592,28 @@ class Remote(object):
host, port = os.environ['REPO_HOST_PORT_INFO'].split() host, port = os.environ['REPO_HOST_PORT_INFO'].split()
self._review_url = self._SshReviewUrl(userEmail, host, port) self._review_url = self._SshReviewUrl(userEmail, host, port)
REVIEW_CACHE[u] = self._review_url 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: else:
try: try:
info_url = u + 'ssh_info' info_url = u + 'ssh_info'
info = urllib2.urlopen(info_url).read() info = urllib.request.urlopen(info_url).read()
if '<' in info: if info == 'NOT_AVAILABLE' or '<' in info:
# Assume the server gave us some sort of HTML # If `info` contains '<', we assume the server gave us some sort
# response back, like maybe a login page. # of HTML response back, like maybe a login page.
# #
raise UploadError('%s: Cannot parse response' % info_url) # Assume HTTP if SSH is not enabled or ssh_info doesn't look right.
self._review_url = http_url
if info == 'NOT_AVAILABLE':
# Assume HTTP if SSH is not enabled.
self._review_url = http_url + 'p/'
else: else:
host, port = info.split() host, port = info.split()
self._review_url = self._SshReviewUrl(userEmail, host, port) self._review_url = self._SshReviewUrl(userEmail, host, port)
except urllib2.HTTPError as e: except urllib.error.HTTPError as e:
raise UploadError('%s: %s' % (self.review, str(e))) raise UploadError('%s: %s' % (self.review, str(e)))
except urllib2.URLError as e: except urllib.error.URLError as e:
raise UploadError('%s: %s' % (self.review, str(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_url REVIEW_CACHE[u] = self._review_url
return self._review_url + self.projectname return self._review_url + self.projectname
@ -645,7 +663,7 @@ class Remote(object):
self._Set('url', self.url) self._Set('url', self.url)
self._Set('review', self.review) self._Set('review', self.review)
self._Set('projectname', self.projectname) 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): def _Set(self, key, value):
key = 'remote.%s.%s' % (self.name, key) key = 'remote.%s.%s' % (self.name, key)

View File

@ -66,7 +66,7 @@ class GitRefs(object):
def _NeedUpdate(self): def _NeedUpdate(self):
Trace(': scan refs %s', self._gitdir) Trace(': scan refs %s', self._gitdir)
for name, mtime in self._mtime.iteritems(): for name, mtime in self._mtime.items():
try: try:
if mtime != os.path.getmtime(os.path.join(self._gitdir, name)): if mtime != os.path.getmtime(os.path.join(self._gitdir, name)):
return True return True
@ -89,7 +89,7 @@ class GitRefs(object):
attempts = 0 attempts = 0
while scan and attempts < 5: while scan and attempts < 5:
scan_next = {} scan_next = {}
for name, dest in scan.iteritems(): for name, dest in scan.items():
if dest in self._phyref: if dest in self._phyref:
self._phyref[name] = self._phyref[dest] self._phyref[name] = self._phyref[dest]
else: else:
@ -100,7 +100,7 @@ class GitRefs(object):
def _ReadPackedRefs(self): def _ReadPackedRefs(self):
path = os.path.join(self._gitdir, 'packed-refs') path = os.path.join(self._gitdir, 'packed-refs')
try: try:
fd = open(path, 'rb') fd = open(path, 'r')
mtime = os.path.getmtime(path) mtime = os.path.getmtime(path)
except IOError: except IOError:
return return
@ -108,6 +108,7 @@ class GitRefs(object):
return return
try: try:
for line in fd: for line in fd:
line = str(line)
if line[0] == '#': if line[0] == '#':
continue continue
if line[0] == '^': if line[0] == '^':
@ -138,18 +139,22 @@ class GitRefs(object):
def _ReadLoose1(self, path, name): def _ReadLoose1(self, path, name):
try: try:
fd = open(path, 'rb') fd = open(path, 'rb')
except: except IOError:
return return
try: try:
try: try:
mtime = os.path.getmtime(path) mtime = os.path.getmtime(path)
ref_id = fd.readline() ref_id = fd.readline()
except: except (IOError, OSError):
return return
finally: finally:
fd.close() fd.close()
try:
ref_id = ref_id.decode()
except AttributeError:
pass
if not ref_id: if not ref_id:
return return
ref_id = ref_id[:-1] ref_id = ref_id[:-1]

View File

@ -1,5 +1,5 @@
#!/bin/sh #!/bin/sh
# From Gerrit Code Review 2.5-rc0 # From Gerrit Code Review 2.6
# #
# Part of Gerrit Code Review (http://code.google.com/p/gerrit/) # Part of Gerrit Code Review (http://code.google.com/p/gerrit/)
# #
@ -18,6 +18,8 @@
# limitations under the License. # limitations under the License.
# #
unset GREP_OPTIONS
CHANGE_ID_AFTER="Bug|Issue" CHANGE_ID_AFTER="Bug|Issue"
MSG="$1" MSG="$1"
@ -152,7 +154,7 @@ add_ChangeId() {
if (unprinted) { if (unprinted) {
print "Change-Id: I'"$id"'" print "Change-Id: I'"$id"'"
} }
}' "$MSG" > $T && mv $T "$MSG" || rm -f $T }' "$MSG" > "$T" && mv "$T" "$MSG" || rm -f "$T"
} }
_gen_ChangeIdInput() { _gen_ChangeIdInput() {
echo "tree `git write-tree`" echo "tree `git write-tree`"

View File

@ -35,7 +35,7 @@ elif grep -q "AC Power \+: 1" /proc/pmu/info 2>/dev/null
then then
exit 0 exit 0
elif test -x /usr/bin/pmset && /usr/bin/pmset -g batt | 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 then
exit 0 exit 0
elif test -d /sys/bus/acpi/drivers/battery && test 0 = \ elif test -d /sys/bus/acpi/drivers/battery && test 0 = \

274
main.py
View File

@ -1,4 +1,4 @@
#!/bin/sh #!/usr/bin/env python
# #
# Copyright (C) 2008 The Android Open Source Project # Copyright (C) 2008 The Android Open Source Project
# #
@ -14,23 +14,27 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
magic='--calling-python-from-/bin/sh--' from __future__ import print_function
"""exec" python -E "$0" "$@" """#$magic"
if __name__ == '__main__':
import sys
if sys.argv[-1] == '#%s' % magic:
del sys.argv[-1]
del magic
import getpass import getpass
import imp import imp
import netrc import netrc
import optparse import optparse
import os import os
import re
import sys import sys
import time import time
import urllib2
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 trace import SetTrace from trace import SetTrace
from git_command import git, GitCommand from git_command import git, GitCommand
@ -41,13 +45,21 @@ from subcmds.version import Version
from editor import Editor from editor import Editor
from error import DownloadError from error import DownloadError
from error import ManifestInvalidRevisionError from error import ManifestInvalidRevisionError
from error import ManifestParseError
from error import NoManifestException
from error import NoSuchProjectError from error import NoSuchProjectError
from error import RepoChangedException from error import RepoChangedException
from manifest_xml import XmlManifest from manifest_xml import XmlManifest
from pager import RunPager from pager import RunPager
from wrapper import WrapperPath, Wrapper
from subcmds import all_commands from subcmds import all_commands
if not is_python3():
# pylint:disable=W0622
input = raw_input
# pylint:enable=W0622
global_options = optparse.OptionParser( global_options = optparse.OptionParser(
usage="repo [-p|--paginate|--no-pager] COMMAND [ARGS]" usage="repo [-p|--paginate|--no-pager] COMMAND [ARGS]"
) )
@ -79,7 +91,7 @@ class _Repo(object):
name = None name = None
glob = [] glob = []
for i in xrange(0, len(argv)): for i in range(len(argv)):
if not argv[i].startswith('-'): if not argv[i].startswith('-'):
name = argv[i] name = argv[i]
if i > 0: if i > 0:
@ -98,15 +110,14 @@ class _Repo(object):
if name == 'help': if name == 'help':
name = 'version' name = 'version'
else: else:
print >>sys.stderr, 'fatal: invalid usage of --version' print('fatal: invalid usage of --version', file=sys.stderr)
return 1 return 1
try: try:
cmd = self.commands[name] cmd = self.commands[name]
except KeyError: except KeyError:
print >>sys.stderr,\ print("repo: '%s' is not a repo command. See 'repo help'." % name,
"repo: '%s' is not a repo command. See 'repo help'."\ file=sys.stderr)
% name
return 1 return 1
cmd.repodir = self.repodir cmd.repodir = self.repodir
@ -114,12 +125,19 @@ class _Repo(object):
Editor.globalConfig = cmd.manifest.globalConfig Editor.globalConfig = cmd.manifest.globalConfig
if not isinstance(cmd, MirrorSafeCommand) and cmd.manifest.IsMirror: if not isinstance(cmd, MirrorSafeCommand) and cmd.manifest.IsMirror:
print >>sys.stderr, \ print("fatal: '%s' requires a working directory" % name,
"fatal: '%s' requires a working directory"\ file=sys.stderr)
% name
return 1 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): if not gopts.no_pager and not isinstance(cmd, InteractiveCommand):
config = cmd.manifest.globalConfig config = cmd.manifest.globalConfig
@ -132,87 +150,75 @@ class _Repo(object):
if use_pager: if use_pager:
RunPager(config) RunPager(config)
start = time.time()
try: try:
start = time.time() result = cmd.Execute(copts, cargs)
try: except (DownloadError, ManifestInvalidRevisionError,
result = cmd.Execute(copts, cargs) NoManifestException) as e:
finally: print('error: in `%s`: %s' % (' '.join([name] + argv), str(e)),
elapsed = time.time() - start file=sys.stderr)
hours, remainder = divmod(elapsed, 3600) if isinstance(e, NoManifestException):
minutes, seconds = divmod(remainder, 60) print('error: manifest missing or unreadable -- please run init',
if gopts.time: file=sys.stderr)
if hours == 0: result = 1
print >>sys.stderr, 'real\t%dm%.3fs' \
% (minutes, seconds)
else:
print >>sys.stderr, 'real\t%dh%dm%.3fs' \
% (hours, minutes, seconds)
except DownloadError as e:
print >>sys.stderr, 'error: %s' % str(e)
return 1
except ManifestInvalidRevisionError as e:
print >>sys.stderr, 'error: %s' % str(e)
return 1
except NoSuchProjectError as e: except NoSuchProjectError as e:
if e.name: 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: else:
print >>sys.stderr, 'error: no project in current directory' print('error: no project in current directory', file=sys.stderr)
return 1 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)
return result return result
def _MyRepoPath(): def _MyRepoPath():
return os.path.dirname(__file__) return os.path.dirname(__file__)
def _MyWrapperPath():
return os.path.join(os.path.dirname(__file__), 'repo')
_wrapper_module = None
def WrapperModule():
global _wrapper_module
if not _wrapper_module:
_wrapper_module = imp.load_source('wrapper', _MyWrapperPath())
return _wrapper_module
def _CurrentWrapperVersion():
return WrapperModule().VERSION
def _CheckWrapperVersion(ver, repo_path): def _CheckWrapperVersion(ver, repo_path):
if not repo_path: if not repo_path:
repo_path = '~/bin/repo' repo_path = '~/bin/repo'
if not ver: if not ver:
print >>sys.stderr, 'no --wrapper-version argument' print('no --wrapper-version argument', file=sys.stderr)
sys.exit(1) sys.exit(1)
exp = _CurrentWrapperVersion() exp = Wrapper().VERSION
ver = tuple(map(lambda x: int(x), ver.split('.'))) ver = tuple(map(int, ver.split('.')))
if len(ver) == 1: if len(ver) == 1:
ver = (0, ver[0]) ver = (0, ver[0])
exp_str = '.'.join(map(str, exp))
if exp[0] > ver[0] or ver < (0, 4): if exp[0] > ver[0] or ver < (0, 4):
exp_str = '.'.join(map(lambda x: str(x), exp)) print("""
print >>sys.stderr, """
!!! A new repo command (%5s) is available. !!! !!! A new repo command (%5s) is available. !!!
!!! You must upgrade before you can continue: !!! !!! You must upgrade before you can continue: !!!
cp %s %s cp %s %s
""" % (exp_str, _MyWrapperPath(), repo_path) """ % (exp_str, WrapperPath(), repo_path), file=sys.stderr)
sys.exit(1) sys.exit(1)
if exp > ver: if exp > ver:
exp_str = '.'.join(map(lambda x: str(x), exp)) print("""
print >>sys.stderr, """
... A new repo command (%5s) is available. ... A new repo command (%5s) is available.
... You should upgrade soon: ... You should upgrade soon:
cp %s %s cp %s %s
""" % (exp_str, _MyWrapperPath(), repo_path) """ % (exp_str, WrapperPath(), repo_path), file=sys.stderr)
def _CheckRepoDir(repo_dir): def _CheckRepoDir(repo_dir):
if not repo_dir: if not repo_dir:
print >>sys.stderr, 'no --repo-dir argument' print('no --repo-dir argument', file=sys.stderr)
sys.exit(1) sys.exit(1)
def _PruneOptions(argv, opt): def _PruneOptions(argv, opt):
@ -264,11 +270,11 @@ def _UserAgent():
_user_agent = 'git-repo/%s (%s) git/%s Python/%d.%d.%d' % ( _user_agent = 'git-repo/%s (%s) git/%s Python/%d.%d.%d' % (
repo_version, repo_version,
os_name, os_name,
'.'.join(map(lambda d: str(d), git.version_tuple())), '.'.join(map(str, git.version_tuple())),
py_version[0], py_version[1], py_version[2]) py_version[0], py_version[1], py_version[2])
return _user_agent return _user_agent
class _UserAgentHandler(urllib2.BaseHandler): class _UserAgentHandler(urllib.request.BaseHandler):
def http_request(self, req): def http_request(self, req):
req.add_header('User-Agent', _UserAgent()) req.add_header('User-Agent', _UserAgent())
return req return req
@ -278,22 +284,22 @@ class _UserAgentHandler(urllib2.BaseHandler):
return req return req
def _AddPasswordFromUserInput(handler, msg, req): def _AddPasswordFromUserInput(handler, msg, req):
# If repo could not find auth info from netrc, try to get it from user input # If repo could not find auth info from netrc, try to get it from user input
url = req.get_full_url() url = req.get_full_url()
user, password = handler.passwd.find_user_password(None, url) user, password = handler.passwd.find_user_password(None, url)
if user is None: if user is None:
print msg print(msg)
try: try:
user = raw_input('User: ') user = input('User: ')
password = getpass.getpass() password = getpass.getpass()
except KeyboardInterrupt: except KeyboardInterrupt:
return return
handler.passwd.add_password(None, url, user, password) handler.passwd.add_password(None, url, user, password)
class _BasicAuthHandler(urllib2.HTTPBasicAuthHandler): class _BasicAuthHandler(urllib.request.HTTPBasicAuthHandler):
def http_error_401(self, req, fp, code, msg, headers): def http_error_401(self, req, fp, code, msg, headers):
_AddPasswordFromUserInput(self, msg, req) _AddPasswordFromUserInput(self, msg, req)
return urllib2.HTTPBasicAuthHandler.http_error_401( return urllib.request.HTTPBasicAuthHandler.http_error_401(
self, req, fp, code, msg, headers) self, req, fp, code, msg, headers)
def http_error_auth_reqed(self, authreq, host, req, headers): def http_error_auth_reqed(self, authreq, host, req, headers):
@ -303,7 +309,7 @@ class _BasicAuthHandler(urllib2.HTTPBasicAuthHandler):
val = val.replace('\n', '') val = val.replace('\n', '')
old_add_header(name, val) old_add_header(name, val)
req.add_header = _add_header req.add_header = _add_header
return urllib2.AbstractBasicAuthHandler.http_error_auth_reqed( return urllib.request.AbstractBasicAuthHandler.http_error_auth_reqed(
self, authreq, host, req, headers) self, authreq, host, req, headers)
except: except:
reset = getattr(self, 'reset_retry_count', None) reset = getattr(self, 'reset_retry_count', None)
@ -313,10 +319,10 @@ class _BasicAuthHandler(urllib2.HTTPBasicAuthHandler):
self.retried = 0 self.retried = 0
raise raise
class _DigestAuthHandler(urllib2.HTTPDigestAuthHandler): class _DigestAuthHandler(urllib.request.HTTPDigestAuthHandler):
def http_error_401(self, req, fp, code, msg, headers): def http_error_401(self, req, fp, code, msg, headers):
_AddPasswordFromUserInput(self, msg, req) _AddPasswordFromUserInput(self, msg, req)
return urllib2.HTTPDigestAuthHandler.http_error_401( return urllib.request.HTTPDigestAuthHandler.http_error_401(
self, req, fp, code, msg, headers) self, req, fp, code, msg, headers)
def http_error_auth_reqed(self, auth_header, host, req, headers): def http_error_auth_reqed(self, auth_header, host, req, headers):
@ -326,7 +332,7 @@ class _DigestAuthHandler(urllib2.HTTPDigestAuthHandler):
val = val.replace('\n', '') val = val.replace('\n', '')
old_add_header(name, val) old_add_header(name, val)
req.add_header = _add_header req.add_header = _add_header
return urllib2.AbstractDigestAuthHandler.http_error_auth_reqed( return urllib.request.AbstractDigestAuthHandler.http_error_auth_reqed(
self, auth_header, host, req, headers) self, auth_header, host, req, headers)
except: except:
reset = getattr(self, 'reset_retry_count', None) reset = getattr(self, 'reset_retry_count', None)
@ -336,10 +342,90 @@ class _DigestAuthHandler(urllib2.HTTPDigestAuthHandler):
self.retried = 0 self.retried = 0
raise 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(): def init_http():
handlers = [_UserAgentHandler()] handlers = [_UserAgentHandler()]
mgr = urllib2.HTTPPasswordMgrWithDefaultRealm() mgr = urllib.request.HTTPPasswordMgrWithDefaultRealm()
try: try:
n = netrc.netrc() n = netrc.netrc()
for host in n.hosts: for host in n.hosts:
@ -352,14 +438,16 @@ def init_http():
pass pass
handlers.append(_BasicAuthHandler(mgr)) handlers.append(_BasicAuthHandler(mgr))
handlers.append(_DigestAuthHandler(mgr)) handlers.append(_DigestAuthHandler(mgr))
if kerberos:
handlers.append(_KerberosAuthHandler())
if 'http_proxy' in os.environ: if 'http_proxy' in os.environ:
url = os.environ['http_proxy'] url = os.environ['http_proxy']
handlers.append(urllib2.ProxyHandler({'http': url, 'https': url})) handlers.append(urllib.request.ProxyHandler({'http': url, 'https': url}))
if 'REPO_CURL_VERBOSE' in os.environ: if 'REPO_CURL_VERBOSE' in os.environ:
handlers.append(urllib2.HTTPHandler(debuglevel=1)) handlers.append(urllib.request.HTTPHandler(debuglevel=1))
handlers.append(urllib2.HTTPSHandler(debuglevel=1)) handlers.append(urllib.request.HTTPSHandler(debuglevel=1))
urllib2.install_opener(urllib2.build_opener(*handlers)) urllib.request.install_opener(urllib.request.build_opener(*handlers))
def _Main(argv): def _Main(argv):
result = 0 result = 0
@ -389,6 +477,10 @@ def _Main(argv):
finally: finally:
close_ssh() close_ssh()
except KeyboardInterrupt: except KeyboardInterrupt:
print('aborted by user', file=sys.stderr)
result = 1
except ManifestParseError as mpe:
print('fatal: %s' % mpe, file=sys.stderr)
result = 1 result = 1
except RepoChangedException as rce: except RepoChangedException as rce:
# If repo changed, re-exec ourselves. # If repo changed, re-exec ourselves.
@ -398,8 +490,8 @@ def _Main(argv):
try: try:
os.execv(__file__, argv) os.execv(__file__, argv)
except OSError as e: except OSError as e:
print >>sys.stderr, 'fatal: cannot restart repo after upgrade' print('fatal: cannot restart repo after upgrade', file=sys.stderr)
print >>sys.stderr, 'fatal: %s' % e print('fatal: %s' % e, file=sys.stderr)
result = 128 result = 128
sys.exit(result) sys.exit(result)

View File

@ -13,31 +13,49 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from __future__ import print_function
import itertools import itertools
import os import os
import re import re
import sys import sys
import urlparse
import xml.dom.minidom import xml.dom.minidom
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_config import GitConfig
from git_refs import R_HEADS, HEAD from git_refs import R_HEADS, HEAD
from project import RemoteSpec, Project, MetaProject from project import RemoteSpec, Project, MetaProject
from error import ManifestParseError from error import ManifestParseError, ManifestInvalidRevisionError
MANIFEST_FILE_NAME = 'manifest.xml' MANIFEST_FILE_NAME = 'manifest.xml'
LOCAL_MANIFEST_NAME = 'local_manifest.xml' LOCAL_MANIFEST_NAME = 'local_manifest.xml'
LOCAL_MANIFESTS_DIR_NAME = 'local_manifests'
urlparse.uses_relative.extend(['ssh', 'git']) urllib.parse.uses_relative.extend(['ssh', 'git'])
urlparse.uses_netloc.extend(['ssh', 'git']) urllib.parse.uses_netloc.extend(['ssh', 'git'])
class _Default(object): class _Default(object):
"""Project defaults within the manifest.""" """Project defaults within the manifest."""
revisionExpr = None revisionExpr = None
destBranchExpr = None
remote = None remote = None
sync_j = 1 sync_j = 1
sync_c = False 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): class _XmlRemote(object):
def __init__(self, def __init__(self,
@ -53,15 +71,30 @@ class _XmlRemote(object):
self.reviewUrl = review self.reviewUrl = review
self.resolvedFetchUrl = self._resolveFetchUrl() 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): def _resolveFetchUrl(self):
url = self.fetchUrl.rstrip('/') url = self.fetchUrl.rstrip('/')
manifestUrl = self.manifestUrl.rstrip('/') manifestUrl = self.manifestUrl.rstrip('/')
# urljoin will get confused if there is no scheme in the base url # urljoin will gets confused over quite a few things. The ones we care
# ie, if manifestUrl is of the form <hostname:port> # about here are:
# * no scheme in the base url, like <hostname:port>
# * persistent-https://
# We handle this by replacing these with obscure protocols
# and then replacing them with the original when we are done.
# gopher -> <none>
# wais -> persistent-https
if manifestUrl.find(':') != manifestUrl.find('/') - 1: if manifestUrl.find(':') != manifestUrl.find('/') - 1:
manifestUrl = 'gopher://' + manifestUrl manifestUrl = 'gopher://' + manifestUrl
url = urlparse.urljoin(manifestUrl, url) manifestUrl = re.sub(r'^persistent-https://', 'wais://', manifestUrl)
return re.sub(r'^gopher://', '', url) url = urllib.parse.urljoin(manifestUrl, url)
url = re.sub(r'^gopher://', '', url)
url = re.sub(r'^wais://', 'persistent-https://', url)
return url
def ToRemoteSpec(self, projectName): def ToRemoteSpec(self, projectName):
url = self.resolvedFetchUrl.rstrip('/') + '/' + projectName url = self.resolvedFetchUrl.rstrip('/') + '/' + projectName
@ -78,6 +111,7 @@ class XmlManifest(object):
self.topdir = os.path.dirname(self.repodir) self.topdir = os.path.dirname(self.repodir)
self.manifestFile = os.path.join(self.repodir, MANIFEST_FILE_NAME) self.manifestFile = os.path.join(self.repodir, MANIFEST_FILE_NAME)
self.globalConfig = GitConfig.ForUser() self.globalConfig = GitConfig.ForUser()
self.localManifestWarning = False
self.repoProject = MetaProject(self, 'repo', self.repoProject = MetaProject(self, 'repo',
gitdir = os.path.join(repodir, 'repo/.git'), gitdir = os.path.join(repodir, 'repo/.git'),
@ -110,17 +144,19 @@ class XmlManifest(object):
self.Override(name) self.Override(name)
try: try:
if os.path.exists(self.manifestFile): if os.path.lexists(self.manifestFile):
os.remove(self.manifestFile) os.remove(self.manifestFile)
os.symlink('manifests/%s' % name, self.manifestFile) os.symlink('manifests/%s' % name, self.manifestFile)
except OSError: except OSError as e:
raise ManifestParseError('cannot link manifest %s' % name) raise ManifestParseError('cannot link manifest %s: %s' % (name, str(e)))
def _RemoteToXml(self, r, doc, root): def _RemoteToXml(self, r, doc, root):
e = doc.createElement('remote') e = doc.createElement('remote')
root.appendChild(e) root.appendChild(e)
e.setAttribute('name', r.name) e.setAttribute('name', r.name)
e.setAttribute('fetch', r.fetchUrl) e.setAttribute('fetch', r.fetchUrl)
if r.remoteAlias is not None:
e.setAttribute('alias', r.remoteAlias)
if r.reviewUrl is not None: if r.reviewUrl is not None:
e.setAttribute('review', r.reviewUrl) e.setAttribute('review', r.reviewUrl)
@ -130,9 +166,8 @@ class XmlManifest(object):
mp = self.manifestProject mp = self.manifestProject
groups = mp.config.GetString('manifest.groups') groups = mp.config.GetString('manifest.groups')
if not groups: if groups:
groups = 'all' groups = [x for x in re.split(r'[,\s]+', groups) if x]
groups = [x for x in re.split(r'[,\s]+', groups) if x]
doc = xml.dom.minidom.Document() doc = xml.dom.minidom.Document()
root = doc.createElement('manifest') root = doc.createElement('manifest')
@ -148,10 +183,8 @@ class XmlManifest(object):
notice_element.appendChild(doc.createTextNode(indented_notice)) notice_element.appendChild(doc.createTextNode(indented_notice))
d = self.default 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) self._RemoteToXml(self.remotes[r], doc, root)
if self.remotes: if self.remotes:
root.appendChild(doc.createTextNode('')) root.appendChild(doc.createTextNode(''))
@ -170,6 +203,9 @@ class XmlManifest(object):
if d.sync_c: if d.sync_c:
have_default = True have_default = True
e.setAttribute('sync-c', 'true') e.setAttribute('sync-c', 'true')
if d.sync_s:
have_default = True
e.setAttribute('sync-s', 'true')
if have_default: if have_default:
root.appendChild(e) root.appendChild(e)
root.appendChild(doc.createTextNode('')) root.appendChild(doc.createTextNode(''))
@ -181,8 +217,9 @@ class XmlManifest(object):
root.appendChild(doc.createTextNode('')) root.appendChild(doc.createTextNode(''))
def output_projects(parent, parent_node, projects): def output_projects(parent, parent_node, projects):
for p in projects: for project_name in projects:
output_project(parent, parent_node, self.projects[p]) for project in self._projects[project_name]:
output_project(parent, parent_node, project)
def output_project(parent, parent_node, p): def output_project(parent, parent_node, p):
if not p.MatchesGroups(groups): if not p.MatchesGroups(groups):
@ -199,7 +236,10 @@ class XmlManifest(object):
e.setAttribute('name', name) e.setAttribute('name', name)
if relpath != name: if relpath != name:
e.setAttribute('path', relpath) e.setAttribute('path', relpath)
if not d.remote or p.remote.name != d.remote.name: remoteName = None
if d.remote:
remoteName = d.remote.remoteAlias or d.remote.name
if not d.remote or p.remote.name != remoteName:
e.setAttribute('remote', p.remote.name) e.setAttribute('remote', p.remote.name)
if peg_rev: if peg_rev:
if self.IsMirror: if self.IsMirror:
@ -221,6 +261,12 @@ class XmlManifest(object):
ce.setAttribute('dest', c.dest) ce.setAttribute('dest', c.dest)
e.appendChild(ce) 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] default_groups = ['all', 'name:%s' % p.name, 'path:%s' % p.relpath]
egroups = [g for g in p.groups if g not in default_groups] egroups = [g for g in p.groups if g not in default_groups]
if egroups: if egroups:
@ -236,15 +282,15 @@ class XmlManifest(object):
if p.sync_c: if p.sync_c:
e.setAttribute('sync-c', 'true') e.setAttribute('sync-c', 'true')
if p.subprojects: if p.sync_s:
sort_projects = [subp.name for subp in p.subprojects] e.setAttribute('sync-s', 'true')
sort_projects.sort()
output_projects(p, e, sort_projects)
sort_projects = [key for key in self.projects.keys() if p.subprojects:
if not self.projects[key].parent] subprojects = set(subp.name for subp in p.subprojects)
sort_projects.sort() output_projects(p, e, list(sorted(subprojects)))
output_projects(None, root, sort_projects)
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: if self._repo_hooks_project:
root.appendChild(doc.createTextNode('')) root.appendChild(doc.createTextNode(''))
@ -256,10 +302,15 @@ class XmlManifest(object):
doc.writexml(fd, '', ' ', '\n', 'UTF-8') doc.writexml(fd, '', ' ', '\n', 'UTF-8')
@property
def paths(self):
self._Load()
return self._paths
@property @property
def projects(self): def projects(self):
self._Load() self._Load()
return self._projects return self._paths.values()
@property @property
def remotes(self): def remotes(self):
@ -290,9 +341,14 @@ class XmlManifest(object):
def IsMirror(self): def IsMirror(self):
return self.manifestProject.config.GetBoolean('repo.mirror') return self.manifestProject.config.GetBoolean('repo.mirror')
@property
def IsArchive(self):
return self.manifestProject.config.GetBoolean('repo.archive')
def _Unload(self): def _Unload(self):
self._loaded = False self._loaded = False
self._projects = {} self._projects = {}
self._paths = {}
self._remotes = {} self._remotes = {}
self._default = None self._default = None
self._repo_hooks_project = None self._repo_hooks_project = None
@ -314,9 +370,29 @@ class XmlManifest(object):
local = os.path.join(self.repodir, LOCAL_MANIFEST_NAME) local = os.path.join(self.repodir, LOCAL_MANIFEST_NAME)
if os.path.exists(local): if os.path.exists(local):
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)) nodes.append(self._ParseManifestXml(local, self.repodir))
self._ParseManifest(nodes) 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: if self.IsMirror:
self._AddMetaProjectMirror(self.repoProject) self._AddMetaProjectMirror(self.repoProject)
@ -325,7 +401,11 @@ class XmlManifest(object):
self._loaded = True self._loaded = True
def _ParseManifestXml(self, path, include_root): def _ParseManifestXml(self, path, include_root):
root = xml.dom.minidom.parse(path) 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: if not root or not root.childNodes:
raise ManifestParseError("no root node in %s" % (path,)) raise ManifestParseError("no root node in %s" % (path,))
@ -338,43 +418,47 @@ class XmlManifest(object):
nodes = [] nodes = []
for node in manifest.childNodes: # pylint:disable=W0631 for node in manifest.childNodes: # pylint:disable=W0631
# We only get here if manifest is initialised # We only get here if manifest is initialised
if node.nodeName == 'include': if node.nodeName == 'include':
name = self._reqatt(node, 'name') name = self._reqatt(node, 'name')
fp = os.path.join(include_root, name) fp = os.path.join(include_root, name)
if not os.path.isfile(fp): if not os.path.isfile(fp):
raise ManifestParseError, \ raise ManifestParseError("include %s doesn't exist or isn't a file"
"include %s doesn't exist or isn't a file" % \ % (name,))
(name,) try:
try: nodes.extend(self._ParseManifestXml(fp, include_root))
nodes.extend(self._ParseManifestXml(fp, include_root)) # should isolate this to the exact exception, but that's
# should isolate this to the exact exception, but that's # tricky. actual parsing implementation may vary.
# tricky. actual parsing implementation may vary. except (KeyboardInterrupt, RuntimeError, SystemExit):
except (KeyboardInterrupt, RuntimeError, SystemExit): raise
raise except Exception as e:
except Exception as e: raise ManifestParseError(
raise ManifestParseError( "failed parsing included manifest %s: %s", (name, e))
"failed parsing included manifest %s: %s", (name, e)) else:
else: nodes.append(node)
nodes.append(node)
return nodes return nodes
def _ParseManifest(self, node_list): def _ParseManifest(self, node_list):
for node in itertools.chain(*node_list): for node in itertools.chain(*node_list):
if node.nodeName == 'remote': if node.nodeName == 'remote':
remote = self._ParseRemote(node) remote = self._ParseRemote(node)
if self._remotes.get(remote.name): if remote:
raise ManifestParseError( if remote.name in self._remotes:
'duplicate remote %s in %s' % if remote != self._remotes[remote.name]:
(remote.name, self.manifestFile)) raise ManifestParseError(
self._remotes[remote.name] = remote 'remote %s already exists with different attributes' %
(remote.name))
else:
self._remotes[remote.name] = remote
for node in itertools.chain(*node_list): for node in itertools.chain(*node_list):
if node.nodeName == 'default': if node.nodeName == 'default':
if self._default is not None: new_default = self._ParseDefault(node)
raise ManifestParseError( if self._default is None:
'duplicate default in %s' % self._default = new_default
(self.manifestFile)) elif new_default != self._default:
self._default = self._ParseDefault(node) raise ManifestParseError('duplicate default in %s' %
(self.manifestFile))
if self._default is None: if self._default is None:
self._default = _Default() self._default = _Default()
@ -390,22 +474,29 @@ class XmlManifest(object):
if node.nodeName == 'manifest-server': if node.nodeName == 'manifest-server':
url = self._reqatt(node, 'url') url = self._reqatt(node, 'url')
if self._manifest_server is not None: if self._manifest_server is not None:
raise ManifestParseError( raise ManifestParseError(
'duplicate manifest-server in %s' % 'duplicate manifest-server in %s' %
(self.manifestFile)) (self.manifestFile))
self._manifest_server = url self._manifest_server = url
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): for node in itertools.chain(*node_list):
if node.nodeName == 'project': if node.nodeName == 'project':
project = self._ParseProject(node) project = self._ParseProject(node)
def recursively_add_projects(project):
if self._projects.get(project.name):
raise ManifestParseError(
'duplicate project %s in %s' %
(project.name, self.manifestFile))
self._projects[project.name] = project
for subproject in project.subprojects:
recursively_add_projects(subproject)
recursively_add_projects(project) recursively_add_projects(project)
if node.nodeName == 'repo-hooks': if node.nodeName == 'repo-hooks':
# Get the name of the project and the (space-separated) list of enabled. # Get the name of the project and the (space-separated) list of enabled.
@ -420,22 +511,30 @@ class XmlManifest(object):
# Store a reference to the Project. # Store a reference to the Project.
try: try:
self._repo_hooks_project = self._projects[repo_hooks_project] repo_hooks_projects = self._projects[repo_hooks_project]
except KeyError: except KeyError:
raise ManifestParseError( raise ManifestParseError(
'project %s not found for repo-hooks' % 'project %s not found for repo-hooks' %
(repo_hooks_project)) (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. # Store the enabled hooks in the Project object.
self._repo_hooks_project.enabled_repo_hooks = enabled_repo_hooks self._repo_hooks_project.enabled_repo_hooks = enabled_repo_hooks
if node.nodeName == 'remove-project': if node.nodeName == 'remove-project':
name = self._reqatt(node, 'name') name = self._reqatt(node, 'name')
try:
del self._projects[name] if name not in self._projects:
except KeyError: raise ManifestParseError('remove-project element specifies non-existent '
raise ManifestParseError( 'project: %s' % name)
'project %s not found' %
(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 # If the manifest removes the hooks project, treat it as if it deleted
# the repo-hooks element too. # the repo-hooks element too.
@ -447,7 +546,7 @@ class XmlManifest(object):
name = None name = None
m_url = m.GetRemote(m.remote.name).url m_url = m.GetRemote(m.remote.name).url
if m_url.endswith('/.git'): 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: if self._default and self._default.remote:
url = self._default.remote.resolvedFetchUrl url = self._default.remote.resolvedFetchUrl
@ -473,11 +572,13 @@ class XmlManifest(object):
name = name, name = name,
remote = remote.ToRemoteSpec(name), remote = remote.ToRemoteSpec(name),
gitdir = gitdir, gitdir = gitdir,
objdir = gitdir,
worktree = None, worktree = None,
relpath = None, relpath = name or None,
revisionExpr = m.revisionExpr, revisionExpr = m.revisionExpr,
revisionId = None) revisionId = None)
self._projects[project.name] = project self._projects[project.name] = [project]
self._paths[project.relpath] = project
def _ParseRemote(self, node): def _ParseRemote(self, node):
""" """
@ -504,6 +605,8 @@ class XmlManifest(object):
if d.revisionExpr == '': if d.revisionExpr == '':
d.revisionExpr = None d.revisionExpr = None
d.destBranchExpr = node.getAttribute('dest-branch') or None
sync_j = node.getAttribute('sync-j') sync_j = node.getAttribute('sync-j')
if sync_j == '' or sync_j is None: if sync_j == '' or sync_j is None:
d.sync_j = 1 d.sync_j = 1
@ -515,6 +618,12 @@ class XmlManifest(object):
d.sync_c = False d.sync_c = False
else: else:
d.sync_c = sync_c.lower() in ("yes", "true", "1") 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 return d
def _ParseNotice(self, node): def _ParseNotice(self, node):
@ -535,7 +644,7 @@ class XmlManifest(object):
# Figure out minimum indentation, skipping the first line (the same line # Figure out minimum indentation, skipping the first line (the same line
# as the <notice> tag)... # as the <notice> tag)...
minIndent = sys.maxint minIndent = sys.maxsize
lines = notice.splitlines() lines = notice.splitlines()
for line in lines[1:]: for line in lines[1:]:
lstrippedLine = line.lstrip() lstrippedLine = line.lstrip()
@ -574,25 +683,22 @@ class XmlManifest(object):
if remote is None: if remote is None:
remote = self._default.remote remote = self._default.remote
if remote is None: if remote is None:
raise ManifestParseError, \ raise ManifestParseError("no remote for project %s within %s" %
"no remote for project %s within %s" % \ (name, self.manifestFile))
(name, self.manifestFile)
revisionExpr = node.getAttribute('revision') revisionExpr = node.getAttribute('revision')
if not revisionExpr: if not revisionExpr:
revisionExpr = self._default.revisionExpr revisionExpr = self._default.revisionExpr
if not revisionExpr: if not revisionExpr:
raise ManifestParseError, \ raise ManifestParseError("no revision for project %s within %s" %
"no revision for project %s within %s" % \ (name, self.manifestFile))
(name, self.manifestFile)
path = node.getAttribute('path') path = node.getAttribute('path')
if not path: if not path:
path = name path = name
if path.startswith('/'): if path.startswith('/'):
raise ManifestParseError, \ raise ManifestParseError("project %s path cannot be absolute in %s" %
"project %s path cannot be absolute in %s" % \ (name, self.manifestFile))
(name, self.manifestFile)
rebase = node.getAttribute('rebase') rebase = node.getAttribute('rebase')
if not rebase: if not rebase:
@ -606,25 +712,49 @@ class XmlManifest(object):
else: else:
sync_c = sync_c.lower() in ("yes", "true", "1") 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') upstream = node.getAttribute('upstream')
groups = '' groups = ''
if node.hasAttribute('groups'): if node.hasAttribute('groups'):
groups = node.getAttribute('groups') groups = node.getAttribute('groups')
groups = [x for x in re.split('[,\s]+', groups) if x] groups = [x for x in re.split(r'[,\s]+', groups) if x]
if parent is None: if parent is None:
relpath, worktree, gitdir = self.GetProjectPaths(name, path) relpath, worktree, gitdir, objdir = self.GetProjectPaths(name, path)
else: else:
relpath, worktree, gitdir = self.GetSubprojectPaths(parent, path) relpath, worktree, gitdir, objdir = \
self.GetSubprojectPaths(parent, name, path)
default_groups = ['all', 'name:%s' % name, 'path:%s' % relpath] default_groups = ['all', 'name:%s' % name, 'path:%s' % relpath]
groups.extend(set(default_groups).difference(groups)) 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, project = Project(manifest = self,
name = name, name = name,
remote = remote.ToRemoteSpec(name), remote = remote.ToRemoteSpec(name),
gitdir = gitdir, gitdir = gitdir,
objdir = objdir,
worktree = worktree, worktree = worktree,
relpath = relpath, relpath = relpath,
revisionExpr = revisionExpr, revisionExpr = revisionExpr,
@ -632,12 +762,17 @@ class XmlManifest(object):
rebase = rebase, rebase = rebase,
groups = groups, groups = groups,
sync_c = sync_c, sync_c = sync_c,
sync_s = sync_s,
clone_depth = clone_depth,
upstream = upstream, upstream = upstream,
parent = parent) parent = parent,
dest_branch = dest_branch)
for n in node.childNodes: for n in node.childNodes:
if n.nodeName == 'copyfile': if n.nodeName == 'copyfile':
self._ParseCopyFile(project, n) self._ParseCopyFile(project, n)
if n.nodeName == 'linkfile':
self._ParseLinkFile(project, n)
if n.nodeName == 'annotation': if n.nodeName == 'annotation':
self._ParseAnnotation(project, n) self._ParseAnnotation(project, n)
if n.nodeName == 'project': if n.nodeName == 'project':
@ -650,10 +785,15 @@ class XmlManifest(object):
if self.IsMirror: if self.IsMirror:
worktree = None worktree = None
gitdir = os.path.join(self.topdir, '%s.git' % name) gitdir = os.path.join(self.topdir, '%s.git' % name)
objdir = gitdir
else: else:
worktree = os.path.join(self.topdir, path).replace('\\', '/') worktree = os.path.join(self.topdir, path).replace('\\', '/')
gitdir = os.path.join(self.repodir, 'projects', '%s.git' % path) gitdir = os.path.join(self.repodir, 'projects', '%s.git' % path)
return relpath, worktree, gitdir 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): def GetSubprojectName(self, parent, submodule_path):
return os.path.join(parent.name, submodule_path) return os.path.join(parent.name, submodule_path)
@ -664,14 +804,15 @@ class XmlManifest(object):
def _UnjoinRelpath(self, parent_relpath, relpath): def _UnjoinRelpath(self, parent_relpath, relpath):
return os.path.relpath(relpath, parent_relpath) return os.path.relpath(relpath, parent_relpath)
def GetSubprojectPaths(self, parent, path): def GetSubprojectPaths(self, parent, name, path):
relpath = self._JoinRelpath(parent.relpath, path) relpath = self._JoinRelpath(parent.relpath, path)
gitdir = os.path.join(parent.gitdir, 'subprojects', '%s.git' % 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: if self.IsMirror:
worktree = None worktree = None
else: else:
worktree = os.path.join(parent.worktree, path).replace('\\', '/') worktree = os.path.join(parent.worktree, path).replace('\\', '/')
return relpath, worktree, gitdir return relpath, worktree, gitdir, objdir
def _ParseCopyFile(self, project, node): def _ParseCopyFile(self, project, node):
src = self._reqatt(node, 'src') src = self._reqatt(node, 'src')
@ -681,6 +822,14 @@ class XmlManifest(object):
# dest is relative to the top of the tree # dest is relative to the top of the tree
project.AddCopyFile(src, dest, os.path.join(self.topdir, dest)) 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): def _ParseAnnotation(self, project, node):
name = self._reqatt(node, 'name') name = self._reqatt(node, 'name')
value = self._reqatt(node, 'value') value = self._reqatt(node, 'value')
@ -689,7 +838,8 @@ class XmlManifest(object):
except ManifestParseError: except ManifestParseError:
keep = "true" keep = "true"
if keep != "true" and keep != "false": if keep != "true" and keep != "false":
raise ManifestParseError, "optional \"keep\" attribute must be \"true\" or \"false\"" raise ManifestParseError('optional "keep" attribute must be '
'"true" or "false"')
project.AddAnnotation(name, value, keep) project.AddAnnotation(name, value, keep)
def _get_remote(self, node): def _get_remote(self, node):
@ -699,9 +849,8 @@ class XmlManifest(object):
v = self._remotes.get(name) v = self._remotes.get(name)
if not v: if not v:
raise ManifestParseError, \ raise ManifestParseError("remote %s not defined in %s" %
"remote %s not defined in %s" % \ (name, self.manifestFile))
(name, self.manifestFile)
return v return v
def _reqatt(self, node, attname): def _reqatt(self, node, attname):
@ -710,7 +859,43 @@ class XmlManifest(object):
""" """
v = node.getAttribute(attname) v = node.getAttribute(attname)
if not v: if not v:
raise ManifestParseError, \ raise ManifestParseError("no %s in <%s> within %s" %
"no %s in <%s> within %s" % \ (attname, node.nodeName, self.manifestFile))
(attname, node.nodeName, self.manifestFile)
return v 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 = fromProjects.keys()
fromKeys.sort()
toKeys = toProjects.keys()
toKeys.sort()
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 # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from __future__ import print_function
import os import os
import select import select
import sys import sys
@ -49,7 +50,7 @@ def RunPager(globalConfig):
_BecomePager(pager) _BecomePager(pager)
except Exception: except Exception:
print >>sys.stderr, "fatal: cannot start pager '%s'" % pager print("fatal: cannot start pager '%s'" % pager, file=sys.stderr)
sys.exit(255) sys.exit(255)
def _SelectPager(globalConfig): def _SelectPager(globalConfig):

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

332
repo
View File

@ -1,9 +1,9 @@
#!/bin/sh #!/usr/bin/env python
## repo default configuration ## repo default configuration
## ##
REPO_URL='https://gerrit.googlesource.com/git-repo' REPO_URL = 'https://gerrit.googlesource.com/git-repo'
REPO_REV='stable' REPO_REV = 'stable'
# Copyright (C) 2008 Google Inc. # Copyright (C) 2008 Google Inc.
# #
@ -19,19 +19,11 @@ REPO_REV='stable'
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # 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 # increment this whenever we make important changes to this script
VERSION = (1, 18) VERSION = (1, 21)
# increment this if the MAINTAINER_KEYS block is modified # increment this if the MAINTAINER_KEYS block is modified
KEYRING_VERSION = (1,1) KEYRING_VERSION = (1, 2)
MAINTAINER_KEYS = """ MAINTAINER_KEYS = """
Repo Maintainer <repo@android.kernel.org> Repo Maintainer <repo@android.kernel.org>
@ -80,49 +72,82 @@ TACbBS+Up3RpfYVfd63c1cDdlru13pQAn3NQy/SN858MkxN+zym86UBgOad2
-----BEGIN PGP PUBLIC KEY BLOCK----- -----BEGIN PGP PUBLIC KEY BLOCK-----
Version: GnuPG v1.4.11 (GNU/Linux) Version: GnuPG v1.4.11 (GNU/Linux)
mQENBFBiLPwBCACvISTASOgFXwADw2GYRH2I2z9RvYkYoZ6ThTTNlMXbbYYKO2Wo mQENBFHRvc8BCADFg45Xx/y6QDC+T7Y/gGc7vx0ww7qfOwIKlAZ9xG3qKunMxo+S
a9LQDNW0TbCEekg5UKk0FD13XOdWaqUt4Gtuvq9c43GRSjMO6NXH+0BjcQ8vUtY2 hPCnzEl3cq+6I1Ww/ndop/HB3N3toPXRCoN8Vs4/Hc7by+SnaLFnacrm+tV5/OgT
/W4CYUevwdo4nQ1+1zsOCu1XYe/CReXq0fdugv3hgmRmh3sz1soo37Q44W2frxxg V37Lzt8lhay1Kl+YfpFwHYYpIEBLFV9knyfRXS/428W2qhdzYfvB15/AasRmwmor
U7Rz3Da4FjgAL0RQ8qndD+LwRHXTY7H7wYM8V/3cYFZV7pSodd75q3MAXYQLf0ZV py4NIzSs8UD/SPr1ihqNCdZM76+MQyN5HMYXW/ALZXUFG0pwluHFA7hrfPG74i8C
QR1XATu5l1QnXrxgHvz7MmDwb1D+jX3YPKnZveaukigQ6hDHdiVcePBiGXmk8LZC zMiP7qvMWIl/r/jtzHioH1dRKgbod+LZsrDJ8mBaqsZaDmNJMhss9g76XvfMyLra
2jQkdXeF7Su1ZYpr2nnEHLJ6vOLcCpPGb8gDABEBAAG0H0NvbmxleSBPd2VucyA8 9DI9/iFuBpGzeqBv0hwOGQspLRrEoyTeR6n1ABEBAAG0H0NvbmxleSBPd2VucyA8
Y2NvM0BhbmRyb2lkLmNvbT6JATgEEwECACIFAlBiLPwCGwMGCwkIBwMCBhUIAgkK Y2NvM0BhbmRyb2lkLmNvbT6JATgEEwECACIFAlHRvc8CGwMGCwkIBwMCBhUIAgkK
CwQWAgMBAh4BAheAAAoJEBkmlFUziHGkHVkH/2Hks2Cif5i2xPtv2IFZcjL42joU CwQWAgMBAh4BAheAAAoJEGe35EhpKzgsP6AIAJKJmNtn4l7hkYHKHFSo3egb6RjQ
T7lO5XFqUYS9ZNHpGa/V0eiPt7rHoO16glR83NZtwlrq2cSN89i9HfOhMYV/qLu8 zEIP3MFTcu8HFX1kF1ZFbrp7xqurLaE53kEkKuAAvjJDAgI8mcZHP1JyplubqjQA
fLCHcV2muw+yCB5s5bxnI5UkToiNZyBNqFkcOt/Kbj9Hpy68A1kmc6myVEaUYebq xvv84gK+OGP3Xk+QK1ZjUQSbjOpjEiSZpRhWcHci3dgOUH4blJfByHw25hlgHowd
2Chx/f3xuEthan099t746v1K+/6SvQGDNctHuaMr9cWdxZtHjdRf31SQRc99Phe5 a/2PrNKZVcJ92YienaxxGjcXEUcd0uYEG2+rwllQigFcnMFDhr9B71MfalRHjFKE
w+ZGR/ebxNDKRK9mKgZT8wVFHlXerJsRqWIqtx1fsW1UgLgbpcpe2MChm6B5wTu0 fmdoypqLrri61YBc59P88Rw2/WUpTQjgNubSqa3A2+CKdaRyaRw+2fdF4TdR0h8W
s1ltzox3l4q71FyRRPUJxXyvGkDLZWpK7EpiHSCOYq/KP3HkKeXU3xqHpcG5AQ0E zbg+lbaPtJHsV+3mJC7fq26MiJDRJa5ZztpMn8su20gbLgi2ShBOaHAYDDi5AQ0E
UGIs/AEIAKzO/7lO9cB6dshmZYo8Vy/b7aGicThE+ChcDSfhvyOXVdEM2GKAjsR+ UdG9zwEIAMoOBq+QLNozAhxOOl5GL3StTStGRgPRXINfmViTsihrqGCWBBUfXlUE
rlBWbTFX3It301p2HwZPFEi9nEvJxVlqqBiW0bPmNMkDRR55l2vbWg35wwkg6RyE OytC0mYcrDUQev/8ToVoyqw+iGSwDkcSXkrEUCKFtHV/GECWtk1keyHgR10YKI1R
Bc5/TQjhXI2w8IvlimoGoUff4t3JmMOnWrnKSvL+5iuRj12p9WmanCHzw3Ee7ztf mquSXoubWGqPeG1PAI74XWaRx8UrL8uCXUtmD8Q5J7mDjKR5NpxaXrwlA0bKsf2E
/aU/q+FTpr3DLerb6S8xbv86ySgnJT6o5CyL2DCWRtnYQyGVi0ZmLzEouAYiO0hs Gp9tu1kKauuToZhWHMRMqYSOGikQJwWSFYKT1KdNcOXLQF6+bfoJ6sjVYdwfmNQL
z0AAu28Mj+12g2WwePRz6gfM9rHtI37ylYW3oT/9M9mO9ei/Bc/1D7Dz6qNV+0vg Ixn8QVhoTDedcqClSWB17VDEFDFa7MmqXZz2qtM3X1R/MUMHqPtegQzBGNhRdnI2
uSVJxM2Bl6GalHPZLhHntFEdIA6EdoUAEQEAAYkBHwQYAQIACQUCUGIs/AIbDAAK V45+1Nnx/uuCxDbeI4RbHzujnxDiq70AEQEAAYkBHwQYAQIACQUCUdG9zwIbDAAK
CRAZJpRVM4hxpNfkB/0W/hP5WK/NETXBlWXXW7JPaWO2c5kGwD0lnj5RRmridyo1 CRBnt+RIaSs4LNVeB/0Y2pZ8I7gAAcEM0Xw8drr4omg2fUoK1J33ozlA/RxeA/lJ
vbm5PdM91jOsDQYqRu6YOoYBnDnEhB2wL2bPh34HWwwrA+LwB8hlcAV2z1bdwyfl I3KnyCDTpXuIeBKPGkdL8uMATC9Z8DnBBajRlftNDVZS3Hz4G09G9QpMojvJkFJV
3R823fReKN3QcvLHzmvZPrF4Rk97M9UIyKS0RtnfTWykRgDWHIsrtQPoNwsXrWoT By+01Flw/X+eeN8NpqSuLV4W+AjEO8at/VvgKr1AFvBRdZ7GkpI1o6DgPe7ZqX+1
9LrM2v+1+9mp3vuXnE473/NHxmiWEQH9Ez+O/mOxQ7rSOlqGRiKq/IBZCfioJOtV dzQZt3e13W0rVBb/bUgx9iSLoeWP3aq/k+/GRGOR+S6F6BBSl0SQ2EF2+dIywb1x
fTQeIu/yASZnsLBqr6SJEGwYBoWcyjG++k4fyw8ocOAo4uGDYbxgN7yYfNQ0OH7o JuinEP+AwLAUZ1Bsx9ISC0Agpk2VeHXPL3FGhroEmoMvBzO0kTFGyoeT7PR/BfKv
V6pfUgqKLWa/aK7/N1ZHnPdFLD8Xt0Dmy4BPwrKC +H/g3HsL2LOB9uoIm8/5p2TTU5ttYCXMHhQZ81AY
=O7am =AUp4
-----END PGP PUBLIC KEY BLOCK----- -----END PGP PUBLIC KEY BLOCK-----
""" """
GIT = 'git' # our git command 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 repodir = '.repo' # name of repo's private directory
S_repo = 'repo' # special repo repository S_repo = 'repo' # special repo repository
S_manifests = 'manifests' # special manifest repository S_manifests = 'manifests' # special manifest repository
REPO_MAIN = S_repo + '/main.py' # main script REPO_MAIN = S_repo + '/main.py' # main script
MIN_PYTHON_VERSION = (2, 6) # minimum supported python version
import errno
import optparse import optparse
import os import os
import re import re
import shutil
import stat
import subprocess import subprocess
import sys import sys
import urllib2
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] == 3:
_print('warning: Python 3 support is currently experimental. YMMV.\n'
'Please use Python 2.6 - 2.7 instead.',
file=sys.stderr)
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') home_dot_repo = os.path.expanduser('~/.repoconfig')
gpg_dir = os.path.join(home_dot_repo, 'gnupg') gpg_dir = os.path.join(home_dot_repo, 'gnupg')
@ -149,16 +174,22 @@ group.add_option('-m', '--manifest-name',
help='initial manifest file', metavar='NAME.xml') help='initial manifest file', metavar='NAME.xml')
group.add_option('--mirror', group.add_option('--mirror',
dest='mirror', action='store_true', 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', group.add_option('--reference',
dest='reference', dest='reference',
help='location of mirror directory', metavar='DIR') help='location of mirror directory', metavar='DIR')
group.add_option('--depth', type='int', default=None, group.add_option('--depth', type='int', default=None,
dest='depth', dest='depth',
help='create a shallow clone with given depth; see git clone') 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', group.add_option('-g', '--groups',
dest='groups', default='default', dest='groups', default='default',
help='restrict manifest projects to ones with a specified group', help='restrict manifest projects to ones with specified '
'group(s) [default|all|G1,G2,G3|G4,-G5,-G6]',
metavar='GROUP') metavar='GROUP')
group.add_option('-p', '--platform', group.add_option('-p', '--platform',
dest='platform', default="auto", dest='platform', default="auto",
@ -211,17 +242,16 @@ def _Init(args):
if branch.startswith('refs/heads/'): if branch.startswith('refs/heads/'):
branch = branch[len('refs/heads/'):] branch = branch[len('refs/heads/'):]
if branch.startswith('refs/'): 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() raise CloneFailure()
if not os.path.isdir(repodir): try:
try: os.mkdir(repodir)
os.mkdir(repodir) except OSError as e:
except OSError as e: if e.errno != errno.EEXIST:
print >>sys.stderr, \ _print('fatal: cannot make %s directory: %s'
'fatal: cannot make %s directory: %s' % ( % (repodir, e.strerror), file=sys.stderr)
repodir, e.strerror) # Don't raise CloneFailure; that would delete the
# Don't faise CloneFailure; that would delete the
# name. Instead exit immediately. # name. Instead exit immediately.
# #
sys.exit(1) sys.exit(1)
@ -244,37 +274,50 @@ def _Init(args):
_Checkout(dst, branch, rev, opt.quiet) _Checkout(dst, branch, rev, opt.quiet)
except CloneFailure: except CloneFailure:
if opt.quiet: if opt.quiet:
print >>sys.stderr, \ _print('fatal: repo init failed; run without --quiet to see why',
'fatal: repo init failed; run without --quiet to see why' file=sys.stderr)
raise 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(): def _CheckGitVersion():
cmd = [GIT, '--version'] cmd = [GIT, '--version']
try: try:
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE) proc = subprocess.Popen(cmd, stdout=subprocess.PIPE)
except OSError as e: except OSError as e:
print >>sys.stderr _print(file=sys.stderr)
print >>sys.stderr, "fatal: '%s' is not available" % GIT _print("fatal: '%s' is not available" % GIT, file=sys.stderr)
print >>sys.stderr, 'fatal: %s' % e _print('fatal: %s' % e, file=sys.stderr)
print >>sys.stderr _print(file=sys.stderr)
print >>sys.stderr, 'Please make sure %s is installed'\ _print('Please make sure %s is installed and in your path.' % GIT,
' and in your path.' % GIT file=sys.stderr)
raise CloneFailure() raise CloneFailure()
ver_str = proc.stdout.read().strip() ver_str = proc.stdout.read().strip()
proc.stdout.close() proc.stdout.close()
proc.wait() proc.wait()
if not ver_str.startswith('git version '): ver_act = ParseGitVersion(ver_str)
print >>sys.stderr, 'error: "%s" unsupported' % ver_str if ver_act is None:
_print('error: "%s" unsupported' % ver_str, file=sys.stderr)
raise CloneFailure() 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: if ver_act < MIN_GIT_VERSION:
need = '.'.join(map(lambda x: str(x), MIN_GIT_VERSION)) need = '.'.join(map(str, MIN_GIT_VERSION))
print >>sys.stderr, 'fatal: git %s or later required' % need _print('fatal: git %s or later required' % need, file=sys.stderr)
raise CloneFailure() raise CloneFailure()
@ -290,29 +333,27 @@ def NeedSetupGnuPG():
if not kv: if not kv:
return True return True
kv = tuple(map(lambda x: int(x), kv.split('.'))) kv = tuple(map(int, kv.split('.')))
if kv < KEYRING_VERSION: if kv < KEYRING_VERSION:
return True return True
return False return False
def SetupGnuPG(quiet): def SetupGnuPG(quiet):
if not os.path.isdir(home_dot_repo): try:
try: os.mkdir(home_dot_repo)
os.mkdir(home_dot_repo) except OSError as e:
except OSError as e: if e.errno != errno.EEXIST:
print >>sys.stderr, \ _print('fatal: cannot make %s directory: %s'
'fatal: cannot make %s directory: %s' % ( % (home_dot_repo, e.strerror), file=sys.stderr)
home_dot_repo, e.strerror)
sys.exit(1) sys.exit(1)
if not os.path.isdir(gpg_dir): try:
try: os.mkdir(gpg_dir, stat.S_IRWXU)
os.mkdir(gpg_dir, 0700) except OSError as e:
except OSError as e: if e.errno != errno.EEXIST:
print >>sys.stderr, \ _print('fatal: cannot make %s directory: %s' % (gpg_dir, e.strerror),
'fatal: cannot make %s directory: %s' % ( file=sys.stderr)
gpg_dir, e.strerror)
sys.exit(1) sys.exit(1)
env = os.environ.copy() env = os.environ.copy()
@ -325,21 +366,21 @@ def SetupGnuPG(quiet):
stdin = subprocess.PIPE) stdin = subprocess.PIPE)
except OSError as e: except OSError as e:
if not quiet: if not quiet:
print >>sys.stderr, 'warning: gpg (GnuPG) is not available.' _print('warning: gpg (GnuPG) is not available.', file=sys.stderr)
print >>sys.stderr, 'warning: Installing it is strongly encouraged.' _print('warning: Installing it is strongly encouraged.', file=sys.stderr)
print >>sys.stderr _print(file=sys.stderr)
return False return False
proc.stdin.write(MAINTAINER_KEYS) proc.stdin.write(MAINTAINER_KEYS)
proc.stdin.close() proc.stdin.close()
if proc.wait() != 0: 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) sys.exit(1)
print _print()
fd = open(os.path.join(home_dot_repo, 'keyring-version'), 'w') 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() fd.close()
return True return True
@ -355,7 +396,7 @@ def _SetConfig(local, name, value):
def _InitHttp(): def _InitHttp():
handlers = [] handlers = []
mgr = urllib2.HTTPPasswordMgrWithDefaultRealm() mgr = urllib.request.HTTPPasswordMgrWithDefaultRealm()
try: try:
import netrc import netrc
n = netrc.netrc() n = netrc.netrc()
@ -365,20 +406,20 @@ def _InitHttp():
mgr.add_password(p[1], 'https://%s/' % host, p[0], p[2]) mgr.add_password(p[1], 'https://%s/' % host, p[0], p[2])
except: except:
pass pass
handlers.append(urllib2.HTTPBasicAuthHandler(mgr)) handlers.append(urllib.request.HTTPBasicAuthHandler(mgr))
handlers.append(urllib2.HTTPDigestAuthHandler(mgr)) handlers.append(urllib.request.HTTPDigestAuthHandler(mgr))
if 'http_proxy' in os.environ: if 'http_proxy' in os.environ:
url = os.environ['http_proxy'] url = os.environ['http_proxy']
handlers.append(urllib2.ProxyHandler({'http': url, 'https': url})) handlers.append(urllib.request.ProxyHandler({'http': url, 'https': url}))
if 'REPO_CURL_VERBOSE' in os.environ: if 'REPO_CURL_VERBOSE' in os.environ:
handlers.append(urllib2.HTTPHandler(debuglevel=1)) handlers.append(urllib.request.HTTPHandler(debuglevel=1))
handlers.append(urllib2.HTTPSHandler(debuglevel=1)) handlers.append(urllib.request.HTTPSHandler(debuglevel=1))
urllib2.install_opener(urllib2.build_opener(*handlers)) urllib.request.install_opener(urllib.request.build_opener(*handlers))
def _Fetch(url, local, src, quiet): def _Fetch(url, local, src, quiet):
if not quiet: if not quiet:
print >>sys.stderr, 'Get %s' % url _print('Get %s' % url, file=sys.stderr)
cmd = [GIT, 'fetch'] cmd = [GIT, 'fetch']
if quiet: if quiet:
@ -423,20 +464,20 @@ def _DownloadBundle(url, local, quiet):
dest = open(os.path.join(local, '.git', 'clone.bundle'), 'w+b') dest = open(os.path.join(local, '.git', 'clone.bundle'), 'w+b')
try: try:
try: try:
r = urllib2.urlopen(url) r = urllib.request.urlopen(url)
except urllib2.HTTPError as e: except urllib.error.HTTPError as e:
if e.code == 404: if e.code in [403, 404]:
return False return False
print >>sys.stderr, 'fatal: Cannot get %s' % url _print('fatal: Cannot get %s' % url, file=sys.stderr)
print >>sys.stderr, 'fatal: HTTP error %s' % e.code _print('fatal: HTTP error %s' % e.code, file=sys.stderr)
raise CloneFailure() raise CloneFailure()
except urllib2.URLError as e: except urllib.error.URLError as e:
print >>sys.stderr, 'fatal: Cannot get %s' % url _print('fatal: Cannot get %s' % url, file=sys.stderr)
print >>sys.stderr, 'fatal: error %s' % e.reason _print('fatal: error %s' % e.reason, file=sys.stderr)
raise CloneFailure() raise CloneFailure()
try: try:
if not quiet: if not quiet:
print >>sys.stderr, 'Get %s' % url _print('Get %s' % url, file=sys.stderr)
while True: while True:
buf = r.read(8192) buf = r.read(8192)
if buf == '': if buf == '':
@ -460,24 +501,23 @@ def _Clone(url, local, quiet):
try: try:
os.mkdir(local) os.mkdir(local)
except OSError as e: except OSError as e:
print >>sys.stderr, \ _print('fatal: cannot make %s directory: %s' % (local, e.strerror),
'fatal: cannot make %s directory: %s' \ file=sys.stderr)
% (local, e.strerror)
raise CloneFailure() raise CloneFailure()
cmd = [GIT, 'init', '--quiet'] cmd = [GIT, 'init', '--quiet']
try: try:
proc = subprocess.Popen(cmd, cwd = local) proc = subprocess.Popen(cmd, cwd = local)
except OSError as e: except OSError as e:
print >>sys.stderr _print(file=sys.stderr)
print >>sys.stderr, "fatal: '%s' is not available" % GIT _print("fatal: '%s' is not available" % GIT, file=sys.stderr)
print >>sys.stderr, 'fatal: %s' % e _print('fatal: %s' % e, file=sys.stderr)
print >>sys.stderr _print(file=sys.stderr)
print >>sys.stderr, 'Please make sure %s is installed'\ _print('Please make sure %s is installed and in your path.' % GIT,
' and in your path.' % GIT file=sys.stderr)
raise CloneFailure() raise CloneFailure()
if proc.wait() != 0: 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() raise CloneFailure()
_InitHttp() _InitHttp()
@ -505,21 +545,18 @@ def _Verify(cwd, branch, quiet):
proc.stderr.close() proc.stderr.close()
if proc.wait() != 0 or not cur: if proc.wait() != 0 or not cur:
print >>sys.stderr _print(file=sys.stderr)
print >>sys.stderr,\ _print("fatal: branch '%s' has not been signed" % branch, file=sys.stderr)
"fatal: branch '%s' has not been signed" \
% branch
raise CloneFailure() raise CloneFailure()
m = re.compile(r'^(.*)-[0-9]{1,}-g[0-9a-f]{1,}$').match(cur) m = re.compile(r'^(.*)-[0-9]{1,}-g[0-9a-f]{1,}$').match(cur)
if m: if m:
cur = m.group(1) cur = m.group(1)
if not quiet: if not quiet:
print >>sys.stderr _print(file=sys.stderr)
print >>sys.stderr, \ _print("info: Ignoring branch '%s'; using tagged release '%s'"
"info: Ignoring branch '%s'; using tagged release '%s'" \ % (branch, cur), file=sys.stderr)
% (branch, cur) _print(file=sys.stderr)
print >>sys.stderr
env = os.environ.copy() env = os.environ.copy()
env['GNUPGHOME'] = gpg_dir.encode() env['GNUPGHOME'] = gpg_dir.encode()
@ -537,10 +574,10 @@ def _Verify(cwd, branch, quiet):
proc.stderr.close() proc.stderr.close()
if proc.wait() != 0: if proc.wait() != 0:
print >>sys.stderr _print(file=sys.stderr)
print >>sys.stderr, out _print(out, file=sys.stderr)
print >>sys.stderr, err _print(err, file=sys.stderr)
print >>sys.stderr _print(file=sys.stderr)
raise CloneFailure() raise CloneFailure()
return '%s^0' % cur return '%s^0' % cur
@ -594,7 +631,7 @@ def _ParseArguments(args):
opt = _Options() opt = _Options()
arg = [] arg = []
for i in xrange(0, len(args)): for i in range(len(args)):
a = args[i] a = args[i]
if a == '-h' or a == '--help': if a == '-h' or a == '--help':
opt.help = True opt.help = True
@ -607,7 +644,7 @@ def _ParseArguments(args):
def _Usage(): def _Usage():
print >>sys.stderr,\ _print(
"""usage: repo COMMAND [ARGS] """usage: repo COMMAND [ARGS]
repo is not yet installed. Use "repo init" to install it here. repo is not yet installed. Use "repo init" to install it here.
@ -618,7 +655,7 @@ The most commonly used repo commands are:
help Display detailed help on a command help Display detailed help on a command
For access to the full online help, install repo ("repo init"). For access to the full online help, install repo ("repo init").
""" """, file=sys.stderr)
sys.exit(1) sys.exit(1)
@ -628,25 +665,23 @@ def _Help(args):
init_optparse.print_help() init_optparse.print_help()
sys.exit(0) sys.exit(0)
else: else:
print >>sys.stderr,\ _print("error: '%s' is not a bootstrap command.\n"
"error: '%s' is not a bootstrap command.\n"\ ' For access to online help, install repo ("repo init").'
' For access to online help, install repo ("repo init").'\ % args[0], file=sys.stderr)
% args[0]
else: else:
_Usage() _Usage()
sys.exit(1) sys.exit(1)
def _NotInstalled(): def _NotInstalled():
print >>sys.stderr,\ _print('error: repo is not installed. Use "repo init" to install it here.',
'error: repo is not installed. Use "repo init" to install it here.' file=sys.stderr)
sys.exit(1) sys.exit(1)
def _NoCommands(cmd): def _NoCommands(cmd):
print >>sys.stderr,\ _print("""error: command '%s' requires repo to be installed first.
"""error: command '%s' requires repo to be installed first. Use "repo init" to install it here.""" % cmd, file=sys.stderr)
Use "repo init" to install it here.""" % cmd
sys.exit(1) sys.exit(1)
@ -683,7 +718,7 @@ def _SetDefaultsTo(gitdir):
proc.stderr.close() proc.stderr.close()
if proc.wait() != 0: 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) sys.exit(1)
@ -707,12 +742,7 @@ def main(orig_args):
try: try:
_Init(args) _Init(args)
except CloneFailure: except CloneFailure:
for root, dirs, files in os.walk(repodir, topdown=False): shutil.rmtree(repodir, ignore_errors=True)
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)
sys.exit(1) sys.exit(1)
repo_main, rel_repo_dir = _FindRepo() repo_main, rel_repo_dir = _FindRepo()
else: else:
@ -721,8 +751,8 @@ def main(orig_args):
if my_main: if my_main:
repo_main = my_main repo_main = my_main
ver_str = '.'.join(map(lambda x: str(x), VERSION)) ver_str = '.'.join(map(str, VERSION))
me = [repo_main, me = [sys.executable, repo_main,
'--repo-dir=%s' % rel_repo_dir, '--repo-dir=%s' % rel_repo_dir,
'--wrapper-version=%s' % ver_str, '--wrapper-version=%s' % ver_str,
'--wrapper-path=%s' % wrapper_path, '--wrapper-path=%s' % wrapper_path,
@ -730,10 +760,10 @@ def main(orig_args):
me.extend(orig_args) me.extend(orig_args)
me.extend(extra_args) me.extend(extra_args)
try: try:
os.execv(repo_main, me) os.execv(sys.executable, me)
except OSError as e: except OSError as e:
print >>sys.stderr, "fatal: unable to start %s" % repo_main _print("fatal: unable to start %s" % repo_main, file=sys.stderr)
print >>sys.stderr, "fatal: %s" % e _print("fatal: %s" % e, file=sys.stderr)
sys.exit(148) sys.exit(148)

View File

@ -38,8 +38,8 @@ for py in os.listdir(my_dir):
try: try:
cmd = getattr(mod, clsn)() cmd = getattr(mod, clsn)()
except AttributeError: except AttributeError:
raise SyntaxError, '%s/%s does not define class %s' % ( raise SyntaxError('%s/%s does not define class %s' % (
__name__, py, clsn) __name__, py, clsn))
name = name.replace('_', '-') name = name.replace('_', '-')
cmd.NAME = name cmd.NAME = name

View File

@ -13,6 +13,7 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from __future__ import print_function
import sys import sys
from command import Command from command import Command
from git_command import git from git_command import git
@ -36,7 +37,7 @@ It is equivalent to "git branch -D <branchname>".
nb = args[0] nb = args[0]
if not git.check_ref_format('heads/%s' % nb): 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) sys.exit(1)
nb = args[0] nb = args[0]
@ -58,13 +59,13 @@ It is equivalent to "git branch -D <branchname>".
if err: if err:
for p in err: for p in err:
print >>sys.stderr,\ print("error: %s/: cannot abandon %s" % (p.relpath, nb),
"error: %s/: cannot abandon %s" \ file=sys.stderr)
% (p.relpath, nb)
sys.exit(1) sys.exit(1)
elif not success: 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) sys.exit(1)
else: else:
print >>sys.stderr, 'Abandoned in %d project(s):\n %s' % ( print('Abandoned in %d project(s):\n %s'
len(success), '\n '.join(p.relpath for p in success)) % (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 # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from __future__ import print_function
import sys import sys
from color import Coloring from color import Coloring
from command import Command from command import Command
@ -97,17 +98,16 @@ is shown, then the branch appears in all projects.
project_cnt = len(projects) project_cnt = len(projects)
for project in projects: for project in projects:
for name, b in project.GetBranches().iteritems(): for name, b in project.GetBranches().items():
b.project = project b.project = project
if name not in all_branches: if name not in all_branches:
all_branches[name] = BranchInfo(name) all_branches[name] = BranchInfo(name)
all_branches[name].add(b) all_branches[name].add(b)
names = all_branches.keys() names = list(sorted(all_branches))
names.sort()
if not names: if not names:
print >>sys.stderr, ' (no branches)' print(' (no branches)', file=sys.stderr)
return return
width = 25 width = 25
@ -139,7 +139,7 @@ is shown, then the branch appears in all projects.
if in_cnt < project_cnt: if in_cnt < project_cnt:
fmt = out.write fmt = out.write
paths = [] paths = []
if in_cnt < project_cnt - in_cnt: if in_cnt < project_cnt - in_cnt:
in_type = 'in' in_type = 'in'
for b in i.projects: for b in i.projects:
paths.append(b.project.relpath) paths.append(b.project.relpath)

View File

@ -13,6 +13,7 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from __future__ import print_function
import sys import sys
from command import Command from command import Command
from progress import Progress from progress import Progress
@ -55,10 +56,9 @@ The command is equivalent to:
if err: if err:
for p in err: for p in err:
print >>sys.stderr,\ print("error: %s/: cannot checkout %s" % (p.relpath, nb),
"error: %s/: cannot checkout %s" \ file=sys.stderr)
% (p.relpath, nb)
sys.exit(1) sys.exit(1)
elif not success: 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) sys.exit(1)

View File

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

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

View File

@ -13,6 +13,7 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from __future__ import print_function
import fcntl import fcntl
import re import re
import os import os
@ -41,10 +42,14 @@ class Forall(Command, MirrorSafeCommand):
helpSummary = "Run a shell command in each project" helpSummary = "Run a shell command in each project"
helpUsage = """ helpUsage = """
%prog [<project>...] -c <command> [<arg>...] %prog [<project>...] -c <command> [<arg>...]
%prog -r str1 [str2] ... -c <command> [<arg>...]"
""" """
helpDescription = """ helpDescription = """
Executes the same shell command in each project. 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 Output Formatting
----------------- -----------------
@ -82,6 +87,12 @@ revision to a locally executed git command, use REPO_LREV.
REPO_RREV is the name of the revision from the manifest, exactly REPO_RREV is the name of the revision from the manifest, exactly
as written in the manifest. 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 REPO__* are any extra environment variables, specified by the
"annotation" element under any project element. This can be useful "annotation" element under any project element. This can be useful
for differentiating trees based on user-specific criteria, or simply for differentiating trees based on user-specific criteria, or simply
@ -92,6 +103,9 @@ following <command>.
Unless -p is used, stdin, stdout, stderr are inherited from the Unless -p is used, stdin, stdout, stderr are inherited from the
terminal and are not redirected. 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): def _Options(self, p):
@ -99,11 +113,17 @@ terminal and are not redirected.
setattr(parser.values, option.dest, list(parser.rargs)) setattr(parser.values, option.dest, list(parser.rargs))
while parser.rargs: while parser.rargs:
del parser.rargs[0] 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', p.add_option('-c', '--command',
help='Command (and arguments) to execute', help='Command (and arguments) to execute',
dest='command', dest='command',
action='callback', action='callback',
callback=cmd) 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 = p.add_option_group('Output')
g.add_option('-p', g.add_option('-p',
@ -159,7 +179,14 @@ terminal and are not redirected.
rc = 0 rc = 0
first = True first = True
for project in self.GetProjects(args): if not opt.regex:
projects = self.GetProjects(args)
else:
projects = self.FindProjects(args)
os.environ['REPO_COUNT'] = str(len(projects))
for (cnt, project) in enumerate(projects):
env = os.environ.copy() env = os.environ.copy()
def setenv(name, val): def setenv(name, val):
if val is None: if val is None:
@ -171,6 +198,7 @@ terminal and are not redirected.
setenv('REPO_REMOTE', project.remote.name) setenv('REPO_REMOTE', project.remote.name)
setenv('REPO_LREV', project.GetRevisionId()) setenv('REPO_LREV', project.GetRevisionId())
setenv('REPO_RREV', project.revisionExpr) setenv('REPO_RREV', project.revisionExpr)
setenv('REPO_I', str(cnt + 1))
for a in project.annotations: for a in project.annotations:
setenv("REPO__%s" % (a.name), a.value) setenv("REPO__%s" % (a.name), a.value)
@ -183,7 +211,7 @@ terminal and are not redirected.
if not os.path.exists(cwd): if not os.path.exists(cwd):
if (opt.project_header and opt.verbose) \ if (opt.project_header and opt.verbose) \
or not opt.project_header: or not opt.project_header:
print >>sys.stderr, 'skipping %s/' % project.relpath print('skipping %s/' % project.relpath, file=sys.stderr)
continue continue
if opt.project_header: if opt.project_header:
@ -241,7 +269,12 @@ terminal and are not redirected.
first = False first = False
else: else:
out.nl() out.nl()
out.project('project %s/', project.relpath)
if mirror:
project_header_path = project.name
else:
project_header_path = project.relpath
out.project('project %s/', project_header_path)
out.nl() out.nl()
out.flush() out.flush()
if errbuf: if errbuf:
@ -254,7 +287,12 @@ terminal and are not redirected.
s.dest.flush() s.dest.flush()
r = p.wait() r = p.wait()
if r != 0 and r != rc: if r != 0:
rc = r if r != rc:
rc = r
if opt.abort_on_errors:
print("error: %s: Aborting due to previous error" % project.relpath,
file=sys.stderr)
sys.exit(r)
if rc != 0: if rc != 0:
sys.exit(rc) sys.exit(rc)

View File

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

View File

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

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,12 +13,22 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from __future__ import print_function
import os import os
import platform import platform
import re import re
import shutil import shutil
import sys 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.urlparse
from color import Coloring from color import Coloring
from command import InteractiveCommand, MirrorSafeCommand from command import InteractiveCommand, MirrorSafeCommand
from error import ManifestParseError from error import ManifestParseError
@ -89,9 +99,14 @@ to update the working directory files.
g.add_option('--depth', type='int', default=None, g.add_option('--depth', type='int', default=None,
dest='depth', dest='depth',
help='create a shallow clone with given depth; see git clone') 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', g.add_option('-g', '--groups',
dest='groups', default='all,-notdefault', dest='groups', default='default',
help='restrict manifest projects to ones with a specified group', help='restrict manifest projects to ones with specified '
'group(s) [default|all|G1,G2,G3|G4,-G5,-G6]',
metavar='GROUP') metavar='GROUP')
g.add_option('-p', '--platform', g.add_option('-p', '--platform',
dest='platform', default='auto', dest='platform', default='auto',
@ -117,19 +132,35 @@ to update the working directory files.
dest='config_name', action="store_true", default=False, dest='config_name', action="store_true", default=False,
help='Always prompt for name/e-mail') help='Always prompt for name/e-mail')
def _RegisteredEnvironmentOptions(self):
return {'REPO_MANIFEST_URL': 'manifest_url',
'REPO_MIRROR_LOCATION': 'reference'}
def _SyncManifest(self, opt): def _SyncManifest(self, opt):
m = self.manifest.manifestProject m = self.manifest.manifestProject
is_new = not m.Exists is_new = not m.Exists
if is_new: if is_new:
if not opt.manifest_url: 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) sys.exit(1)
if not opt.quiet: if not opt.quiet:
print >>sys.stderr, 'Get %s' \ print('Get %s' % GitConfig.ForUser().UrlInsteadOf(opt.manifest_url),
% GitConfig.ForUser().UrlInsteadOf(opt.manifest_url) file=sys.stderr)
m._InitGitDir()
# 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(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: if opt.manifest_branch:
m.revisionExpr = opt.manifest_branch m.revisionExpr = opt.manifest_branch
@ -147,7 +178,7 @@ to update the working directory files.
r.ResetFetch() r.ResetFetch()
r.Save() r.Save()
groups = re.split('[,\s]+', opt.groups) groups = re.split(r'[,\s]+', opt.groups)
all_platforms = ['linux', 'darwin'] all_platforms = ['linux', 'darwin']
platformize = lambda x: 'platform-' + x platformize = lambda x: 'platform-' + x
if opt.platform == 'auto': if opt.platform == 'auto':
@ -159,28 +190,41 @@ to update the working directory files.
elif opt.platform in all_platforms: elif opt.platform in all_platforms:
groups.extend(platformize(opt.platform)) groups.extend(platformize(opt.platform))
elif opt.platform != 'none': elif opt.platform != 'none':
print >>sys.stderr, 'fatal: invalid platform flag' print('fatal: invalid platform flag', file=sys.stderr)
sys.exit(1) sys.exit(1)
groups = [x for x in groups if x] groups = [x for x in groups if x]
groupstr = ','.join(groups) groupstr = ','.join(groups)
if opt.platform == 'auto' and groupstr == 'all,-notdefault,platform-' + platform.system().lower(): if opt.platform == 'auto' and groupstr == 'default,platform-' + platform.system().lower():
groupstr = None groupstr = None
m.config.SetString('manifest.groups', groupstr) m.config.SetString('manifest.groups', groupstr)
if opt.reference: if opt.reference:
m.config.SetString('repo.reference', 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 opt.mirror:
if is_new: if is_new:
m.config.SetString('repo.mirror', 'true') m.config.SetString('repo.mirror', 'true')
else: 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) sys.exit(1)
if not m.Sync_NetworkHalf(is_new=is_new): if not m.Sync_NetworkHalf(is_new=is_new):
r = m.GetRemote(m.remote.name) 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 # 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. # time (when user fixes problems) we won't go through the "is_new" logic.
@ -197,19 +241,19 @@ to update the working directory files.
if is_new or m.CurrentBranch is None: if is_new or m.CurrentBranch is None:
if not m.StartBranch('default'): 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) sys.exit(1)
def _LinkManifest(self, name): def _LinkManifest(self, name):
if not 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) sys.exit(1)
try: try:
self.manifest.Link(name) self.manifest.Link(name)
except ManifestParseError as e: except ManifestParseError as e:
print >>sys.stderr, "fatal: manifest '%s' not available" % name print("fatal: manifest '%s' not available" % name, file=sys.stderr)
print >>sys.stderr, 'fatal: %s' % str(e) print('fatal: %s' % str(e), file=sys.stderr)
sys.exit(1) sys.exit(1)
def _Prompt(self, prompt, value): def _Prompt(self, prompt, value):
@ -231,24 +275,24 @@ to update the working directory files.
mp.config.SetString('user.name', gc.GetString('user.name')) mp.config.SetString('user.name', gc.GetString('user.name'))
mp.config.SetString('user.email', gc.GetString('user.email')) mp.config.SetString('user.email', gc.GetString('user.email'))
print '' print()
print 'Your identity is: %s <%s>' % (mp.config.GetString('user.name'), print('Your identity is: %s <%s>' % (mp.config.GetString('user.name'),
mp.config.GetString('user.email')) mp.config.GetString('user.email')))
print 'If you want to change this, please re-run \'repo init\' with --config-name' print('If you want to change this, please re-run \'repo init\' with --config-name')
return False return False
def _ConfigureUser(self): def _ConfigureUser(self):
mp = self.manifest.manifestProject mp = self.manifest.manifestProject
while True: while True:
print '' print()
name = self._Prompt('Your Name', mp.UserName) name = self._Prompt('Your Name', mp.UserName)
email = self._Prompt('Your Email', mp.UserEmail) email = self._Prompt('Your Email', mp.UserEmail)
print '' print()
print 'Your identity is: %s <%s>' % (name, email) print('Your identity is: %s <%s>' % (name, email))
sys.stdout.write('is this correct [y/N]? ') sys.stdout.write('is this correct [y/N]? ')
a = sys.stdin.readline().strip() a = sys.stdin.readline().strip().lower()
if a in ('yes', 'y', 't', 'true'): if a in ('yes', 'y', 't', 'true'):
break break
@ -274,17 +318,17 @@ to update the working directory files.
self._on = True self._on = True
out = _Test() out = _Test()
print '' print()
print "Testing colorized output (for 'repo diff', 'repo status'):" 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.write(' ')
out.printer(fg=c)(' %-6s ', c) out.printer(fg=c)(' %-6s ', c)
out.write(' ') out.write(' ')
out.printer(fg='white', bg='black')(' %s ' % 'white') out.printer(fg='white', bg='black')(' %s ' % 'white')
out.nl() out.nl()
for c in ['bold','dim','ul','reverse']: for c in ['bold', 'dim', 'ul', 'reverse']:
out.write(' ') out.write(' ')
out.printer(fg='black', attr=c)(' %-6s ', c) out.printer(fg='black', attr=c)(' %-6s ', c)
out.nl() out.nl()
@ -313,12 +357,36 @@ to update the working directory files.
# We store the depth in the main manifest project. # We store the depth in the main manifest project.
self.manifest.manifestProject.config.SetString('repo.depth', depth) 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): def Execute(self, opt, args):
git_require(MIN_GIT_VERSION, fail=True) git_require(MIN_GIT_VERSION, fail=True)
if opt.reference: if opt.reference:
opt.reference = os.path.expanduser(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._SyncManifest(opt)
self._LinkManifest(opt.manifest_name) self._LinkManifest(opt.manifest_name)
@ -329,10 +397,4 @@ to update the working directory files.
self._ConfigureDepth(opt) self._ConfigureDepth(opt)
if self.manifest.IsMirror: self._DisplayResult()
init_type = 'mirror '
else:
init_type = ''
print ''
print 'repo %sinitialized in %s' % (init_type, self.manifest.topdir)

View File

@ -13,7 +13,8 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
import re from __future__ import print_function
import sys
from command import Command, MirrorSafeCommand from command import Command, MirrorSafeCommand
@ -30,13 +31,19 @@ List all projects; pass '.' to list the project for the cwd.
This is similar to running: repo forall -c 'echo "$REPO_PATH : $REPO_PROJECT"'. This is similar to running: repo forall -c 'echo "$REPO_PATH : $REPO_PROJECT"'.
""" """
def _Options(self, p, show_smart=True): def _Options(self, p):
p.add_option('-r', '--regex', p.add_option('-r', '--regex',
dest='regex', action='store_true', dest='regex', action='store_true',
help="Filter the project list based on regex or wildcard matching of strings") help="Filter the project list based on regex or wildcard matching of strings")
p.add_option('-f', '--fullpath', p.add_option('-f', '--fullpath',
dest='fullpath', action='store_true', dest='fullpath', action='store_true',
help="Display the full work tree path instead of the relative path") 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): def Execute(self, opt, args):
"""List all projects and the associated directories. """List all projects and the associated directories.
@ -49,6 +56,11 @@ This is similar to running: repo forall -c 'echo "$REPO_PATH : $REPO_PROJECT"'.
opt: The options. opt: The options.
args: Positional args. Can be a list of projects to list, or empty. args: Positional args. Can be a list of projects to list, or empty.
""" """
if opt.fullpath and opt.name_only:
print('error: cannot combine -f and -n', file=sys.stderr)
sys.exit(1)
if not opt.regex: if not opt.regex:
projects = self.GetProjects(args) projects = self.GetProjects(args)
else: else:
@ -61,18 +73,12 @@ This is similar to running: repo forall -c 'echo "$REPO_PATH : $REPO_PROJECT"'.
lines = [] lines = []
for project in projects: for project in projects:
lines.append("%s : %s" % (_getpath(project), 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() lines.sort()
print '\n'.join(lines) print('\n'.join(lines))
def FindProjects(self, args):
result = []
for project in self.GetProjects(''):
for arg in args:
pattern = re.compile(r'%s' % arg, re.IGNORECASE)
if pattern.search(project.name) or pattern.search(project.relpath):
result.append(project)
break
result.sort(key=lambda project: project.relpath)
return result

View File

@ -13,6 +13,7 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from __future__ import print_function
import os import os
import sys import sys
@ -69,7 +70,7 @@ in a Git repository for use during future 'repo init' invocations.
peg_rev_upstream = opt.peg_rev_upstream) peg_rev_upstream = opt.peg_rev_upstream)
fd.close() fd.close()
if opt.output_file != '-': 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): def Execute(self, opt, args):
if args: if args:
@ -79,6 +80,6 @@ in a Git repository for use during future 'repo init' invocations.
self._Output(opt) self._Output(opt)
return return
print >>sys.stderr, 'error: no operation to perform' print('error: no operation to perform', file=sys.stderr)
print >>sys.stderr, 'error: see repo help manifest' print('error: see repo help manifest', file=sys.stderr)
sys.exit(1) sys.exit(1)

View File

@ -13,6 +13,7 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from __future__ import print_function
from color import Coloring from color import Coloring
from command import PagedCommand from command import PagedCommand
@ -41,7 +42,7 @@ are displayed.
all_branches = [] all_branches = []
for project in self.GetProjects(args): for project in self.GetProjects(args):
br = [project.GetUploadableBranch(x) br = [project.GetUploadableBranch(x)
for x in project.GetBranches().keys()] for x in project.GetBranches()]
br = [x for x in br if x] br = [x for x in br if x]
if opt.current_branch: if opt.current_branch:
br = [x for x in br if x.name == project.CurrentBranch] br = [x for x in br if x.name == project.CurrentBranch]
@ -54,8 +55,11 @@ are displayed.
def __init__(self, config): def __init__(self, config):
Coloring.__init__(self, config, 'status') Coloring.__init__(self, config, 'status')
self.project = self.printer('header', attr='bold') self.project = self.printer('header', attr='bold')
self.text = self.printer('text')
out = Report(all_branches[0].project.config) out = Report(all_branches[0].project.config)
out.text("Deprecated. See repo info -o.")
out.nl()
out.project('Projects Overview') out.project('Projects Overview')
out.nl() out.nl()
@ -70,11 +74,11 @@ are displayed.
commits = branch.commits commits = branch.commits
date = branch.date 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 == project.CurrentBranch and '*' or ' ',
branch.name, branch.name,
len(commits), len(commits),
len(commits) != 1 and 's' or ' ', len(commits) != 1 and 's' or ' ',
date) date))
for commit in commits: for commit in commits:
print '%-35s - %s' % ('', commit) print('%-35s - %s' % ('', commit))

View File

@ -13,6 +13,7 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from __future__ import print_function
from color import Coloring from color import Coloring
from command import PagedCommand from command import PagedCommand
@ -51,9 +52,9 @@ class Prune(PagedCommand):
commits = branch.commits commits = branch.commits
date = branch.date 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 == project.CurrentBranch and '*' or ' ',
branch.name, branch.name,
len(commits), len(commits),
len(commits) != 1 and 's' or ' ', len(commits) != 1 and 's' or ' ',
date) date))

View File

@ -13,6 +13,7 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from __future__ import print_function
import sys import sys
from command import Command from command import Command
@ -59,14 +60,19 @@ branch but need to incorporate new upstream changes "underneath" them.
one_project = len(all_projects) == 1 one_project = len(all_projects) == 1
if opt.interactive and not one_project: 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 return -1
for project in all_projects: for project in all_projects:
cb = project.CurrentBranch cb = project.CurrentBranch
if not cb: if not cb:
if one_project: 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 return -1
# ignore branches with detatched HEADs # ignore branches with detatched HEADs
continue continue
@ -74,7 +80,8 @@ branch but need to incorporate new upstream changes "underneath" them.
upbranch = project.GetBranch(cb) upbranch = project.GetBranch(cb)
if not upbranch.LocalMerge: if not upbranch.LocalMerge:
if one_project: 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 return -1
# ignore branches without remotes # ignore branches without remotes
continue continue
@ -101,8 +108,8 @@ branch but need to incorporate new upstream changes "underneath" them.
args.append(upbranch.LocalMerge) args.append(upbranch.LocalMerge)
print >>sys.stderr, '# %s: rebasing %s -> %s' % \ print('# %s: rebasing %s -> %s'
(project.relpath, cb, upbranch.LocalMerge) % (project.relpath, cb, upbranch.LocalMerge), file=sys.stderr)
needs_stash = False needs_stash = False
if opt.auto_stash: if opt.auto_stash:

View File

@ -13,6 +13,7 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from __future__ import print_function
from optparse import SUPPRESS_HELP from optparse import SUPPRESS_HELP
import sys import sys
@ -52,7 +53,7 @@ need to be performed by an end-user.
else: else:
if not rp.Sync_NetworkHalf(): 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) sys.exit(1)
rp.bare_git.gc('--auto') rp.bare_git.gc('--auto')

View File

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

View File

@ -13,6 +13,7 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from __future__ import print_function
import sys import sys
from command import Command from command import Command
from git_config import IsId from git_config import IsId
@ -41,7 +42,7 @@ revision specified in the manifest.
nb = args[0] nb = args[0]
if not git.check_ref_format('heads/%s' % nb): 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) sys.exit(1)
err = [] err = []
@ -49,7 +50,7 @@ revision specified in the manifest.
if not opt.all: if not opt.all:
projects = args[1:] projects = args[1:]
if len(projects) < 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) sys.exit(1)
all_projects = self.GetProjects(projects) all_projects = self.GetProjects(projects)
@ -67,7 +68,6 @@ revision specified in the manifest.
if err: if err:
for p in err: for p in err:
print >>sys.stderr,\ print("error: %s/: cannot start %s" % (p.relpath, nb),
"error: %s/: cannot start %s" \ file=sys.stderr)
% (p.relpath, nb)
sys.exit(1) sys.exit(1)

View File

@ -20,9 +20,19 @@ try:
except ImportError: except ImportError:
import dummy_threading as _threading import dummy_threading as _threading
import glob
from pyversion import is_python3
if is_python3():
import io
else:
import StringIO as io
import itertools import itertools
import os
import sys import sys
import StringIO
from color import Coloring
class Status(PagedCommand): class Status(PagedCommand):
common = True common = True
@ -39,6 +49,13 @@ is a difference between these three states.
The -j/--jobs option can be used to run multiple status queries The -j/--jobs option can be used to run multiple status queries
in parallel. 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 Status Display
-------------- --------------
@ -76,6 +93,9 @@ the following meanings:
p.add_option('-j', '--jobs', p.add_option('-j', '--jobs',
dest='jobs', action='store', type='int', default=2, dest='jobs', action='store', type='int', default=2,
help="number of projects to check simultaneously") 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, output):
"""Obtains the status for a specific project. """Obtains the status for a specific project.
@ -97,6 +117,22 @@ the following meanings:
finally: finally:
sem.release() 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.write(''.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.write(''.join([status_header, item, '/']))
def Execute(self, opt, args): def Execute(self, opt, args):
all_projects = self.GetProjects(args) all_projects = self.GetProjects(args)
counter = itertools.count() counter = itertools.count()
@ -112,7 +148,7 @@ the following meanings:
for project in all_projects: for project in all_projects:
sem.acquire() sem.acquire()
class BufList(StringIO.StringIO): class BufList(io.StringIO):
def dump(self, ostream): def dump(self, ostream):
for entry in self.buflist: for entry in self.buflist:
ostream.write(entry) ostream.write(entry)
@ -129,4 +165,46 @@ the following meanings:
output.dump(sys.stdout) output.dump(sys.stdout)
output.close() output.close()
if len(all_projects) == counter.next(): if len(all_projects) == counter.next():
print 'nothing to commit (working directory clean)' 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 = io.StringIO()
self._FindOrphans(glob.glob('.*') + \
glob.glob('*'), \
proj_dirs, proj_dirs_parents, outstring)
if outstring.buflist:
output = StatusColoring(self.manifest.globalConfig)
output.project('Objects not within a project (orphans)')
output.nl()
for entry in outstring.buflist:
output.untracked(entry)
output.nl()
else:
print('No orphan files or directories')
outstring.close()
finally:
# Restore CWD.
os.chdir(orig_path)

View File

@ -13,6 +13,7 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from __future__ import print_function
import netrc import netrc
from optparse import SUPPRESS_HELP from optparse import SUPPRESS_HELP
import os import os
@ -23,8 +24,19 @@ import socket
import subprocess import subprocess
import sys import sys
import time import time
import urlparse
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: try:
import threading as _threading import threading as _threading
@ -44,15 +56,15 @@ try:
except ImportError: except ImportError:
multiprocessing = None multiprocessing = None
from git_command import GIT from git_command import GIT, git_require
from git_refs import R_HEADS, HEAD from git_refs import R_HEADS, HEAD
from main import WrapperModule
from project import Project from project import Project
from project import RemoteSpec from project import RemoteSpec
from command import Command, MirrorSafeCommand from command import Command, MirrorSafeCommand
from error import RepoChangedException, GitError from error import RepoChangedException, GitError, ManifestParseError
from project import SyncBuffer from project import SyncBuffer
from progress import Progress from progress import Progress
from wrapper import Wrapper
_ONE_DAY_S = 24 * 60 * 60 _ONE_DAY_S = 24 * 60 * 60
@ -113,6 +125,9 @@ resumeable bundle file on a content delivery network. This
may be necessary if there are problems with the local Python may be necessary if there are problems with the local Python
HTTP client or proxy configuration, but the Git binary works. HTTP client or proxy configuration, but the Git binary works.
The --fetch-submodules option enables fetching Git submodules
of a project from server.
SSH Connections SSH Connections
--------------- ---------------
@ -144,27 +159,30 @@ later is required to fix a server side protocol bug.
""" """
def _Options(self, p, show_smart=True): def _Options(self, p, show_smart=True):
self.jobs = self.manifest.default.sync_j try:
self.jobs = self.manifest.default.sync_j
except ManifestParseError:
self.jobs = 1
p.add_option('-f', '--force-broken', p.add_option('-f', '--force-broken',
dest='force_broken', action='store_true', dest='force_broken', action='store_true',
help="continue sync even if a project fails to sync") 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', dest='local_only', action='store_true',
help="only update working tree, don't fetch") 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', dest='network_only', action='store_true',
help="fetch only, don't update working tree") help="fetch only, don't update working tree")
p.add_option('-d','--detach', p.add_option('-d', '--detach',
dest='detach_head', action='store_true', dest='detach_head', action='store_true',
help='detach projects back to manifest revision') help='detach projects back to manifest revision')
p.add_option('-c','--current-branch', p.add_option('-c', '--current-branch',
dest='current_branch_only', action='store_true', dest='current_branch_only', action='store_true',
help='fetch only current branch from server') help='fetch only current branch from server')
p.add_option('-q','--quiet', p.add_option('-q', '--quiet',
dest='quiet', action='store_true', dest='quiet', action='store_true',
help='be more quiet') help='be more quiet')
p.add_option('-j','--jobs', p.add_option('-j', '--jobs',
dest='jobs', action='store', type='int', dest='jobs', action='store', type='int',
help="projects to fetch simultaneously (default %d)" % self.jobs) help="projects to fetch simultaneously (default %d)" % self.jobs)
p.add_option('-m', '--manifest-name', p.add_option('-m', '--manifest-name',
@ -173,6 +191,18 @@ later is required to fix a server side protocol bug.
p.add_option('--no-clone-bundle', p.add_option('--no-clone-bundle',
dest='no_clone_bundle', action='store_true', dest='no_clone_bundle', action='store_true',
help='disable use of /clone.bundle on HTTP/HTTPS') 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")
if show_smart: if show_smart:
p.add_option('-s', '--smart-sync', p.add_option('-s', '--smart-sync',
dest='smart_sync', action='store_true', dest='smart_sync', action='store_true',
@ -180,12 +210,6 @@ later is required to fix a server side protocol bug.
p.add_option('-t', '--smart-tag', p.add_option('-t', '--smart-tag',
dest='smart_tag', action='store', dest='smart_tag', action='store',
help='smart sync using manifest from a known tag') help='smart sync using manifest from a known tag')
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')
g = p.add_option_group('repo Version options') g = p.add_option_group('repo Version options')
g.add_option('--no-repo-verify', g.add_option('--no-repo-verify',
@ -195,130 +219,156 @@ later is required to fix a server side protocol bug.
dest='repo_upgraded', action='store_true', dest='repo_upgraded', action='store_true',
help=SUPPRESS_HELP) 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): 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: Args:
opt: Program options returned from optparse. See _Options(). opt: Program options returned from optparse. See _Options().
project: Project object for the project to fetch. project: Project object for the project to fetch.
lock: Lock for accessing objects that are shared amongst multiple lock: Lock for accessing objects that are shared amongst multiple
_FetchHelper() threads. _FetchHelper() threads.
fetched: set object that we will add project.gitdir to when we're done fetched: set object that we will add project.gitdir to when we're done
(with our lock held). (with our lock held).
pm: Instance of a Project object. We will call pm.update() (with our pm: Instance of a Project object. We will call pm.update() (with our
lock held). lock held).
sem: We'll release() this semaphore when we exit so that another thread sem: We'll release() this semaphore when we exit so that another thread
can be started up. can be started up.
err_event: We'll set this event in the case of an error (after printing err_event: We'll set this event in the case of an error (after printing
out info about the error). out info about the error).
"""
# We'll set to true once we've locked the lock.
did_lock = False
# Encapsulate everything in a try/except/finally so that: Returns:
# - We always set err_event in the case of an exception. Whether the fetch was successful.
# - We always make sure we call sem.release(). """
# - We always make sure we unlock the lock if we locked it. # 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:
try: start = time.time()
start = time.time() success = project.Sync_NetworkHalf(
success = project.Sync_NetworkHalf( quiet=opt.quiet,
quiet=opt.quiet, current_branch_only=opt.current_branch_only,
current_branch_only=opt.current_branch_only, clone_bundle=not opt.no_clone_bundle,
clone_bundle=not opt.no_clone_bundle) no_tags=opt.no_tags, archive=self.manifest.IsArchive)
self._fetch_times.Set(project, time.time() - start) self._fetch_times.Set(project, time.time() - start)
# Lock around all the rest of the code, since printing, updating a set # Lock around all the rest of the code, since printing, updating a set
# and Progress.update() are not thread safe. # and Progress.update() are not thread safe.
lock.acquire() lock.acquire()
did_lock = True did_lock = True
if not success: if not success:
print >>sys.stderr, 'error: Cannot fetch %s' % project.name print('error: Cannot fetch %s' % project.name, file=sys.stderr)
if opt.force_broken: if opt.force_broken:
print >>sys.stderr, 'warn: --force-broken, continuing to sync' print('warn: --force-broken, continuing to sync',
else: file=sys.stderr)
raise _FetchError() else:
raise _FetchError()
fetched.add(project.gitdir) fetched.add(project.gitdir)
pm.update() pm.update()
except _FetchError: except _FetchError:
err_event.set() err_event.set()
except: except:
err_event.set() err_event.set()
raise raise
finally: finally:
if did_lock: if did_lock:
lock.release() lock.release()
sem.release() sem.release()
return success
def _Fetch(self, projects, opt): def _Fetch(self, projects, opt):
fetched = set() fetched = set()
lock = _threading.Lock()
pm = Progress('Fetching projects', len(projects)) pm = Progress('Fetching projects', len(projects))
if self.jobs == 1: objdir_project_map = dict()
for project in projects: for project in projects:
pm.update() objdir_project_map.setdefault(project.objdir, []).append(project)
if project.Sync_NetworkHalf(
quiet=opt.quiet,
current_branch_only=opt.current_branch_only,
clone_bundle=not opt.no_clone_bundle):
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
sem.acquire() threads = set()
t = _threading.Thread(target = self._FetchHelper, sem = _threading.Semaphore(self.jobs)
args = (opt, err_event = _threading.Event()
project, for project_list in objdir_project_map.values():
lock, # Check for any errors before running any more tasks.
fetched, # ...we'll let existing threads finish, though.
pm, if err_event.isSet() and not opt.force_broken:
sem, break
err_event))
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. # Ensure that Ctrl-C will not freeze the repo process.
t.daemon = True t.daemon = True
threads.add(t) threads.add(t)
t.start() t.start()
else:
self._FetchProjectList(**kwargs)
for t in threads: for t in threads:
t.join() t.join()
# If we saw an error, exit with code 1 so that other scripts can check. # If we saw an error, exit with code 1 so that other scripts can check.
if err_event.isSet(): if err_event.isSet():
print >>sys.stderr, '\nerror: Exited sync due to fetch errors' print('\nerror: Exited sync due to fetch errors', file=sys.stderr)
sys.exit(1) sys.exit(1)
pm.end() pm.end()
self._fetch_times.Save() self._fetch_times.Save()
self._GCProjects(projects) if not self.manifest.IsArchive:
self._GCProjects(projects)
return fetched return fetched
def _GCProjects(self, projects): def _GCProjects(self, projects):
if multiprocessing: 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() cpu_count = multiprocessing.cpu_count()
else: else:
cpu_count = 1 cpu_count = 1
jobs = min(self.jobs, cpu_count) jobs = min(self.jobs, cpu_count)
if jobs < 2: if jobs < 2:
for project in projects: for bare_git in gitdirs.values():
project.bare_git.gc('--auto') bare_git.gc('--auto')
return return
config = {'pack.threads': cpu_count / jobs if cpu_count > jobs else 1} config = {'pack.threads': cpu_count / jobs if cpu_count > jobs else 1}
@ -327,10 +377,10 @@ later is required to fix a server side protocol bug.
sem = _threading.Semaphore(jobs) sem = _threading.Semaphore(jobs)
err_event = _threading.Event() err_event = _threading.Event()
def GC(project): def GC(bare_git):
try: try:
try: try:
project.bare_git.gc('--auto', config=config) bare_git.gc('--auto', config=config)
except GitError: except GitError:
err_event.set() err_event.set()
except: except:
@ -339,11 +389,11 @@ later is required to fix a server side protocol bug.
finally: finally:
sem.release() sem.release()
for project in projects: for bare_git in gitdirs.values():
if err_event.isSet(): if err_event.isSet():
break break
sem.acquire() sem.acquire()
t = _threading.Thread(target=GC, args=(project,)) t = _threading.Thread(target=GC, args=(bare_git,))
t.daemon = True t.daemon = True
threads.add(t) threads.add(t)
t.start() t.start()
@ -352,9 +402,16 @@ later is required to fix a server side protocol bug.
t.join() t.join()
if err_event.isSet(): if err_event.isSet():
print >>sys.stderr, '\nerror: Exited sync due to gc errors' print('\nerror: Exited sync due to gc errors', file=sys.stderr)
sys.exit(1) 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): def UpdateProjectList(self):
new_project_paths = [] new_project_paths = []
for project in self.GetProjects(None, missing_ok=True): for project in self.GetProjects(None, missing_ok=True):
@ -376,34 +433,37 @@ later is required to fix a server side protocol bug.
if path not in new_project_paths: 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): if os.path.exists(self.manifest.topdir + '/' + path):
project = Project( gitdir = os.path.join(self.manifest.topdir, path, '.git')
manifest = self.manifest, project = Project(
name = path, manifest = self.manifest,
remote = RemoteSpec('origin'), name = path,
gitdir = os.path.join(self.manifest.topdir, remote = RemoteSpec('origin'),
path, '.git'), gitdir = gitdir,
worktree = os.path.join(self.manifest.topdir, path), objdir = gitdir,
relpath = path, worktree = os.path.join(self.manifest.topdir, path),
revisionExpr = 'HEAD', relpath = path,
revisionId = None, revisionExpr = 'HEAD',
groups = None) revisionId = None,
groups = None)
if project.IsDirty(): if project.IsDirty():
print >>sys.stderr, 'error: Cannot remove project "%s": \ print('error: Cannot remove project "%s": uncommitted changes '
uncommitted changes are present' % project.relpath 'are present' % project.relpath, file=sys.stderr)
print >>sys.stderr, ' commit changes, then run sync again' print(' commit changes, then run sync again',
return -1 file=sys.stderr)
else: return -1
print >>sys.stderr, 'Deleting obsolete path %s' % project.worktree else:
shutil.rmtree(project.worktree) print('Deleting obsolete path %s' % project.worktree,
# Try deleting parent subdirs if they are empty file=sys.stderr)
project_dir = os.path.dirname(project.worktree) shutil.rmtree(project.worktree)
while project_dir != self.manifest.topdir: # Try deleting parent subdirs if they are empty
try: project_dir = os.path.dirname(project.worktree)
os.rmdir(project_dir) while project_dir != self.manifest.topdir:
except OSError: try:
break os.rmdir(project_dir)
project_dir = os.path.dirname(project_dir) except OSError:
break
project_dir = os.path.dirname(project_dir)
new_project_paths.sort() new_project_paths.sort()
fd = open(file_path, 'w') fd = open(file_path, 'w')
@ -422,36 +482,40 @@ uncommitted changes are present' % project.relpath
self.jobs = min(self.jobs, (soft_limit - 5) / 3) self.jobs = min(self.jobs, (soft_limit - 5) / 3)
if opt.network_only and opt.detach_head: 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) sys.exit(1)
if opt.network_only and opt.local_only: 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) sys.exit(1)
if opt.manifest_name and opt.smart_sync: if opt.manifest_name and opt.smart_sync:
print >>sys.stderr, 'error: cannot combine -m and -s' print('error: cannot combine -m and -s', file=sys.stderr)
sys.exit(1) sys.exit(1)
if opt.manifest_name and opt.smart_tag: if opt.manifest_name and opt.smart_tag:
print >>sys.stderr, 'error: cannot combine -m and -t' print('error: cannot combine -m and -t', file=sys.stderr)
sys.exit(1) sys.exit(1)
if opt.manifest_server_username or opt.manifest_server_password: if opt.manifest_server_username or opt.manifest_server_password:
if not (opt.smart_sync or opt.smart_tag): if not (opt.smart_sync or opt.smart_tag):
print >>sys.stderr, 'error: -u and -p may only be combined with ' \ print('error: -u and -p may only be combined with -s or -t',
'-s or -t' file=sys.stderr)
sys.exit(1) sys.exit(1)
if None in [opt.manifest_server_username, opt.manifest_server_password]: if None in [opt.manifest_server_username, opt.manifest_server_password]:
print >>sys.stderr, 'error: both -u and -p must be given' print('error: both -u and -p must be given', file=sys.stderr)
sys.exit(1) sys.exit(1)
if opt.manifest_name: if opt.manifest_name:
self.manifest.Override(opt.manifest_name) self.manifest.Override(opt.manifest_name)
manifest_name = opt.manifest_name
if opt.smart_sync or opt.smart_tag: if opt.smart_sync or opt.smart_tag:
if not self.manifest.manifest_server: if not self.manifest.manifest_server:
print >>sys.stderr, \ print('error: cannot smart sync: no manifest server defined in '
'error: cannot smart sync: no manifest server defined in manifest' 'manifest', file=sys.stderr)
sys.exit(1) sys.exit(1)
manifest_server = self.manifest.manifest_server manifest_server = self.manifest.manifest_server
if not opt.quiet:
print('Using manifest server %s' % manifest_server)
if not '@' in manifest_server: if not '@' in manifest_server:
username = None username = None
@ -463,20 +527,21 @@ uncommitted changes are present' % project.relpath
try: try:
info = netrc.netrc() info = netrc.netrc()
except IOError: except IOError:
print >>sys.stderr, '.netrc file does not exist or could not be opened' print('.netrc file does not exist or could not be opened',
file=sys.stderr)
else: else:
try: try:
parse_result = urlparse.urlparse(manifest_server) parse_result = urllib.parse.urlparse(manifest_server)
if parse_result.hostname: if parse_result.hostname:
username, _account, password = \ username, _account, password = \
info.authenticators(parse_result.hostname) info.authenticators(parse_result.hostname)
except TypeError: except TypeError:
# TypeError is raised when the given hostname is not present # TypeError is raised when the given hostname is not present
# in the .netrc file. # in the .netrc file.
print >>sys.stderr, 'No credentials found for %s in .netrc' % \ print('No credentials found for %s in .netrc'
parse_result.hostname % parse_result.hostname, file=sys.stderr)
except netrc.NetrcParseError as e: except netrc.NetrcParseError as e:
print >>sys.stderr, 'Error parsing .netrc file: %s' % e print('Error parsing .netrc file: %s' % e, file=sys.stderr)
if (username and password): if (username and password):
manifest_server = manifest_server.replace('://', '://%s:%s@' % manifest_server = manifest_server.replace('://', '://%s:%s@' %
@ -484,7 +549,7 @@ uncommitted changes are present' % project.relpath
1) 1)
try: try:
server = xmlrpclib.Server(manifest_server) server = xmlrpc.client.Server(manifest_server)
if opt.smart_sync: if opt.smart_sync:
p = self.manifest.manifestProject p = self.manifest.manifestProject
b = p.GetBranch(p.CurrentBranch) b = p.GetBranch(p.CurrentBranch)
@ -493,8 +558,7 @@ uncommitted changes are present' % project.relpath
branch = branch[len(R_HEADS):] branch = branch[len(R_HEADS):]
env = os.environ.copy() env = os.environ.copy()
if (env.has_key('TARGET_PRODUCT') and if 'TARGET_PRODUCT' in env and 'TARGET_BUILD_VARIANT' in env:
env.has_key('TARGET_BUILD_VARIANT')):
target = '%s-%s' % (env['TARGET_PRODUCT'], target = '%s-%s' % (env['TARGET_PRODUCT'],
env['TARGET_BUILD_VARIANT']) env['TARGET_BUILD_VARIANT'])
[success, manifest_str] = server.GetApprovedManifest(branch, target) [success, manifest_str] = server.GetApprovedManifest(branch, target)
@ -515,20 +579,22 @@ uncommitted changes are present' % project.relpath
finally: finally:
f.close() f.close()
except IOError: except IOError:
print >>sys.stderr, 'error: cannot write manifest to %s' % \ print('error: cannot write manifest to %s' % manifest_path,
manifest_path file=sys.stderr)
sys.exit(1) sys.exit(1)
self.manifest.Override(manifest_name) self._ReloadManifest(manifest_name)
else: else:
print >>sys.stderr, 'error: %s' % manifest_str print('error: manifest server RPC call failed: %s' %
manifest_str, file=sys.stderr)
sys.exit(1) sys.exit(1)
except (socket.error, IOError, xmlrpclib.Fault) as e: except (socket.error, IOError, xmlrpc.client.Fault) as e:
print >>sys.stderr, 'error: cannot connect to manifest server %s:\n%s' % ( print('error: cannot connect to manifest server %s:\n%s'
self.manifest.manifest_server, e) % (self.manifest.manifest_server, e), file=sys.stderr)
sys.exit(1) sys.exit(1)
except xmlrpclib.ProtocolError as e: except xmlrpc.client.ProtocolError as e:
print >>sys.stderr, 'error: cannot connect to manifest server %s:\n%d %s' % ( print('error: cannot connect to manifest server %s:\n%d %s'
self.manifest.manifest_server, e.errcode, e.errmsg) % (self.manifest.manifest_server, e.errcode, e.errmsg),
file=sys.stderr)
sys.exit(1) sys.exit(1)
rp = self.manifest.repoProject rp = self.manifest.repoProject
@ -538,21 +604,24 @@ uncommitted changes are present' % project.relpath
mp.PreSync() mp.PreSync()
if opt.repo_upgraded: if opt.repo_upgraded:
_PostRepoUpgrade(self.manifest, opt) _PostRepoUpgrade(self.manifest, quiet=opt.quiet)
if not opt.local_only: if not opt.local_only:
mp.Sync_NetworkHalf(quiet=opt.quiet, mp.Sync_NetworkHalf(quiet=opt.quiet,
current_branch_only=opt.current_branch_only) current_branch_only=opt.current_branch_only,
no_tags=opt.no_tags)
if mp.HasChanges: if mp.HasChanges:
syncbuf = SyncBuffer(mp.config) syncbuf = SyncBuffer(mp.config)
mp.Sync_LocalHalf(syncbuf) mp.Sync_LocalHalf(syncbuf)
if not syncbuf.Finish(): if not syncbuf.Finish():
sys.exit(1) sys.exit(1)
self.manifest._Unload() self._ReloadManifest(manifest_name)
if opt.jobs is None: if opt.jobs is None:
self.jobs = self.manifest.default.sync_j self.jobs = self.manifest.default.sync_j
all_projects = self.GetProjects(args, missing_ok=True) all_projects = self.GetProjects(args,
missing_ok=True,
submodules_ok=opt.fetch_submodules)
self._fetch_times = _FetchTimes(self.manifest) self._fetch_times = _FetchTimes(self.manifest)
if not opt.local_only: if not opt.local_only:
@ -572,8 +641,10 @@ uncommitted changes are present' % project.relpath
# Iteratively fetch missing and/or nested unregistered submodules # Iteratively fetch missing and/or nested unregistered submodules
previously_missing_set = set() previously_missing_set = set()
while True: while True:
self.manifest._Unload() self._ReloadManifest(manifest_name)
all_projects = self.GetProjects(args, missing_ok=True) all_projects = self.GetProjects(args,
missing_ok=True,
submodules_ok=opt.fetch_submodules)
missing = [] missing = []
for project in all_projects: for project in all_projects:
if project.gitdir not in fetched: if project.gitdir not in fetched:
@ -588,7 +659,7 @@ uncommitted changes are present' % project.relpath
previously_missing_set = missing_set previously_missing_set = missing_set
fetched.update(self._Fetch(missing, opt)) 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 # bail out now, we have no working tree
return return
@ -603,46 +674,47 @@ uncommitted changes are present' % project.relpath
if project.worktree: if project.worktree:
project.Sync_LocalHalf(syncbuf) project.Sync_LocalHalf(syncbuf)
pm.end() pm.end()
print >>sys.stderr print(file=sys.stderr)
if not syncbuf.Finish(): if not syncbuf.Finish():
sys.exit(1) sys.exit(1)
# If there's a notice that's supposed to print at the end of the sync, print # If there's a notice that's supposed to print at the end of the sync, print
# it now... # it now...
if self.manifest.notice: if self.manifest.notice:
print self.manifest.notice print(self.manifest.notice)
def _PostRepoUpgrade(manifest, opt): def _PostRepoUpgrade(manifest, quiet=False):
wrapper = WrapperModule() wrapper = Wrapper()
if wrapper.NeedSetupGnuPG(): if wrapper.NeedSetupGnuPG():
wrapper.SetupGnuPG(opt.quiet) wrapper.SetupGnuPG(quiet)
for project in manifest.projects.values(): for project in manifest.projects:
if project.Exists: if project.Exists:
project.PostRepoUpgrade() project.PostRepoUpgrade()
def _PostRepoFetch(rp, no_repo_verify=False, verbose=False): def _PostRepoFetch(rp, no_repo_verify=False, verbose=False):
if rp.HasChanges: if rp.HasChanges:
print >>sys.stderr, 'info: A new version of repo is available' print('info: A new version of repo is available', file=sys.stderr)
print >>sys.stderr, '' print(file=sys.stderr)
if no_repo_verify or _VerifyTag(rp): if no_repo_verify or _VerifyTag(rp):
syncbuf = SyncBuffer(rp.config) syncbuf = SyncBuffer(rp.config)
rp.Sync_LocalHalf(syncbuf) rp.Sync_LocalHalf(syncbuf)
if not syncbuf.Finish(): if not syncbuf.Finish():
sys.exit(1) 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']) raise RepoChangedException(['--repo-upgraded'])
else: else:
print >>sys.stderr, 'warning: Skipped upgrade to unverified version' print('warning: Skipped upgrade to unverified version', file=sys.stderr)
else: else:
if verbose: 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): def _VerifyTag(project):
gpg_dir = os.path.expanduser('~/.repoconfig/gnupg') gpg_dir = os.path.expanduser('~/.repoconfig/gnupg')
if not os.path.exists(gpg_dir): if not os.path.exists(gpg_dir):
print >>sys.stderr,\ print('warning: GnuPG was not available during last "repo init"\n'
"""warning: GnuPG was not available during last "repo init" 'warning: Cannot automatically authenticate repo."""',
warning: Cannot automatically authenticate repo.""" file=sys.stderr)
return True return True
try: try:
@ -656,10 +728,9 @@ warning: Cannot automatically authenticate repo."""
if rev.startswith(R_HEADS): if rev.startswith(R_HEADS):
rev = rev[len(R_HEADS):] rev = rev[len(R_HEADS):]
print >>sys.stderr print(file=sys.stderr)
print >>sys.stderr,\ print("warning: project '%s' branch '%s' is not signed"
"warning: project '%s' branch '%s' is not signed" \ % (project.name, rev), file=sys.stderr)
% (project.name, rev)
return False return False
env = os.environ.copy() env = os.environ.copy()
@ -678,10 +749,10 @@ warning: Cannot automatically authenticate repo."""
proc.stderr.close() proc.stderr.close()
if proc.wait() != 0: if proc.wait() != 0:
print >>sys.stderr print(file=sys.stderr)
print >>sys.stderr, out print(out, file=sys.stderr)
print >>sys.stderr, err print(err, file=sys.stderr)
print >>sys.stderr print(file=sys.stderr)
return False return False
return True return True
@ -708,14 +779,14 @@ class _FetchTimes(object):
def _Load(self): def _Load(self):
if self._times is None: if self._times is None:
try: try:
f = open(self._path) f = open(self._path, 'rb')
except IOError: except IOError:
self._times = {} self._times = {}
return self._times return self._times
try: try:
try: try:
self._times = pickle.load(f) self._times = pickle.load(f)
except: except IOError:
try: try:
os.remove(self._path) os.remove(self._path)
except OSError: except OSError:

View File

@ -13,6 +13,7 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from __future__ import print_function
import copy import copy
import re import re
import sys import sys
@ -20,22 +21,31 @@ import sys
from command import InteractiveCommand from command import InteractiveCommand
from editor import Editor from editor import Editor
from error import HookError, UploadError from error import HookError, UploadError
from git_command import GitCommand
from project import RepoHook from project import RepoHook
from pyversion import is_python3
if not is_python3():
# pylint:disable=W0622
input = raw_input
# pylint:enable=W0622
UNUSUAL_COMMIT_THRESHOLD = 5 UNUSUAL_COMMIT_THRESHOLD = 5
def _ConfirmManyUploads(multiple_branches=False): def _ConfirmManyUploads(multiple_branches=False):
if multiple_branches: 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: else:
print "ATTENTION: You are uploading an unusually high number of commits." 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?)" print('YOU PROBABLY DO NOT MEAN TO DO THIS. (Did you rebase across '
answer = raw_input("If you are sure you intend to do this, type 'yes': ").strip() 'branches?)')
answer = input("If you are sure you intend to do this, type 'yes': ").strip()
return answer == "yes" return answer == "yes"
def _die(fmt, *args): def _die(fmt, *args):
msg = fmt % args msg = fmt % args
print >>sys.stderr, 'error: %s' % msg print('error: %s' % msg, file=sys.stderr)
sys.exit(1) sys.exit(1)
def _SplitEmails(values): def _SplitEmails(values):
@ -47,7 +57,7 @@ def _SplitEmails(values):
class Upload(InteractiveCommand): class Upload(InteractiveCommand):
common = True common = True
helpSummary = "Upload changes for code review" helpSummary = "Upload changes for code review"
helpUsage=""" helpUsage = """
%prog [--re --cc] [<project>]... %prog [--re --cc] [<project>]...
""" """
helpDescription = """ helpDescription = """
@ -79,6 +89,11 @@ 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 and will not prompt you further. If it is set to "false" then repo
will assume you always answer "n", and will abort. 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: review.URL.autocopy:
To automatically copy a user or mailing list to all uploaded reviews, To automatically copy a user or mailing list to all uploaded reviews,
@ -137,6 +152,10 @@ Gerrit Code Review: http://code.google.com/p/gerrit/
p.add_option('-d', '--draft', p.add_option('-d', '--draft',
action='store_true', dest='draft', default=False, action='store_true', dest='draft', default=False,
help='If specified, upload as a draft.') 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 # 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. # opposites of each other, which is why they store to different locations.
@ -176,18 +195,19 @@ Gerrit Code Review: http://code.google.com/p/gerrit/
date = branch.date date = branch.date
commit_list = branch.commits commit_list = branch.commits
print 'Upload project %s/ to remote branch %s:' % (project.relpath, project.revisionExpr) destination = opt.dest_branch or project.dest_branch or project.revisionExpr
print ' branch %s (%2d commit%s, %s):' % ( print('Upload project %s/ to remote branch %s:' % (project.relpath, destination))
print(' branch %s (%2d commit%s, %s):' % (
name, name,
len(commit_list), len(commit_list),
len(commit_list) != 1 and 's' or '', len(commit_list) != 1 and 's' or '',
date) date))
for commit in commit_list: for commit in commit_list:
print ' %s' % commit print(' %s' % commit)
sys.stdout.write('to %s (y/N)? ' % remote.review) sys.stdout.write('to %s (y/N)? ' % remote.review)
answer = sys.stdin.readline().strip() answer = sys.stdin.readline().strip().lower()
answer = answer in ('y', 'Y', 'yes', '1', 'true', 't') answer = answer in ('y', 'yes', '1', 'true', 't')
if answer: if answer:
if len(branch.commits) > UNUSUAL_COMMIT_THRESHOLD: if len(branch.commits) > UNUSUAL_COMMIT_THRESHOLD:
@ -210,18 +230,21 @@ Gerrit Code Review: http://code.google.com/p/gerrit/
b = {} b = {}
for branch in avail: for branch in avail:
if branch is None:
continue
name = branch.name name = branch.name
date = branch.date date = branch.date
commit_list = branch.commits commit_list = branch.commits
if b: if b:
script.append('#') script.append('#')
destination = opt.dest_branch or project.dest_branch or project.revisionExpr
script.append('# branch %s (%2d commit%s, %s) to remote branch %s:' % ( script.append('# branch %s (%2d commit%s, %s) to remote branch %s:' % (
name, name,
len(commit_list), len(commit_list),
len(commit_list) != 1 and 's' or '', len(commit_list) != 1 and 's' or '',
date, date,
project.revisionExpr)) destination))
for commit in commit_list: for commit in commit_list:
script.append('# %s' % commit) script.append('# %s' % commit)
b[name] = branch b[name] = branch
@ -275,14 +298,20 @@ Gerrit Code Review: http://code.google.com/p/gerrit/
self._UploadAndReport(opt, todo, people) 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 Appends the list of users in the CC list in the git project's config if a
non-empty reviewer list was found. non-empty reviewer list was found.
""" """
name = branch.name name = branch.name
project = branch.project 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 key = 'review.%s.autocopy' % project.GetBranch(name).remote.review
raw_list = project.config.GetString(key) raw_list = project.config.GetString(key)
if not raw_list is None and len(people[0]) > 0: if not raw_list is None and len(people[0]) > 0:
@ -297,7 +326,7 @@ Gerrit Code Review: http://code.google.com/p/gerrit/
try: try:
# refs/changes/XYZ/N --> XYZ # refs/changes/XYZ/N --> XYZ
return refs.get(last_pub).split('/')[-2] return refs.get(last_pub).split('/')[-2]
except: except (AttributeError, IndexError):
return "" return ""
def _UploadAndReport(self, opt, todo, original_people): def _UploadAndReport(self, opt, todo, original_people):
@ -305,37 +334,52 @@ Gerrit Code Review: http://code.google.com/p/gerrit/
for branch in todo: for branch in todo:
try: try:
people = copy.deepcopy(original_people) people = copy.deepcopy(original_people)
self._AppendAutoCcList(branch, people) self._AppendAutoList(branch, people)
# Check if there are local changes that may have been forgotten # Check if there are local changes that may have been forgotten
if branch.project.HasChanges(): if branch.project.HasChanges():
key = 'review.%s.autoupload' % branch.project.remote.review key = 'review.%s.autoupload' % branch.project.remote.review
answer = branch.project.config.GetBoolean(key) answer = branch.project.config.GetBoolean(key)
# if they want to auto upload, let's not ask because it could be automated # if they want to auto upload, let's not ask because it could be automated
if answer is None: if answer is None:
sys.stdout.write('Uncommitted changes in ' + branch.project.name + ' (did you forget to amend?). Continue uploading? (y/N) ') sys.stdout.write('Uncommitted changes in ' + branch.project.name + ' (did you forget to amend?). Continue uploading? (y/N) ')
a = sys.stdin.readline().strip().lower() a = sys.stdin.readline().strip().lower()
if a not in ('y', 'yes', 't', 'true', 'on'): if a not in ('y', 'yes', 't', 'true', 'on'):
print >>sys.stderr, "skipping upload" print("skipping upload", file=sys.stderr)
branch.uploaded = False branch.uploaded = False
branch.error = 'User aborted' branch.error = 'User aborted'
continue continue
# Check if topic branches should be sent to the server during upload # Check if topic branches should be sent to the server during upload
if opt.auto_topic is not True: if opt.auto_topic is not True:
key = 'review.%s.uploadtopic' % branch.project.remote.review key = 'review.%s.uploadtopic' % branch.project.remote.review
opt.auto_topic = branch.project.config.GetBoolean(key) opt.auto_topic = branch.project.config.GetBoolean(key)
branch.UploadForReview(people, auto_topic=opt.auto_topic, draft=opt.draft) 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 branch.uploaded = True
except UploadError as e: except UploadError as e:
branch.error = e branch.error = e
branch.uploaded = False branch.uploaded = False
have_errors = True have_errors = True
print >>sys.stderr, '' print(file=sys.stderr)
print >>sys.stderr, '----------------------------------------------------------------------' print('----------------------------------------------------------------------', file=sys.stderr)
if have_errors: if have_errors:
for branch in todo: for branch in todo:
@ -344,21 +388,38 @@ Gerrit Code Review: http://code.google.com/p/gerrit/
fmt = ' (%s)' fmt = ' (%s)'
else: else:
fmt = '\n (%s)' fmt = '\n (%s)'
print >>sys.stderr, ('[FAILED] %-15s %-15s' + fmt) % ( print(('[FAILED] %-15s %-15s' + fmt) % (
branch.project.relpath + '/', \ branch.project.relpath + '/', \
branch.name, \ branch.name, \
str(branch.error)) str(branch.error)),
print >>sys.stderr, '' file=sys.stderr)
print()
for branch in todo: for branch in todo:
if branch.uploaded: if branch.uploaded:
print >>sys.stderr, '[OK ] %-15s %s' % ( print('[OK ] %-15s %s' % (
branch.project.relpath + '/', branch.project.relpath + '/',
branch.name) branch.name),
file=sys.stderr)
if have_errors: if have_errors:
sys.exit(1) 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): def Execute(self, opt, args):
project_list = self.GetProjects(args) project_list = self.GetProjects(args)
pending = [] pending = []
@ -372,7 +433,16 @@ Gerrit Code Review: http://code.google.com/p/gerrit/
for project in project_list: for project in project_list:
if opt.current_branch: if opt.current_branch:
cbr = project.CurrentBranch cbr = project.CurrentBranch
avail = [project.GetUploadableBranch(cbr)] if cbr else None 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: else:
avail = project.GetUploadableBranches(branch) avail = project.GetUploadableBranches(branch)
if avail: if avail:
@ -382,20 +452,22 @@ Gerrit Code Review: http://code.google.com/p/gerrit/
hook = RepoHook('pre-upload', self.manifest.repo_hooks_project, hook = RepoHook('pre-upload', self.manifest.repo_hooks_project,
self.manifest.topdir, abort_if_user_denies=True) self.manifest.topdir, abort_if_user_denies=True)
pending_proj_names = [project.name for (project, avail) in pending] pending_proj_names = [project.name for (project, avail) in pending]
pending_worktrees = [project.worktree for (project, avail) in pending]
try: try:
hook.Run(opt.allow_all_hooks, project_list=pending_proj_names) hook.Run(opt.allow_all_hooks, project_list=pending_proj_names,
worktree_list=pending_worktrees)
except HookError as e: except HookError as e:
print >>sys.stderr, "ERROR: %s" % str(e) print("ERROR: %s" % str(e), file=sys.stderr)
return return
if opt.reviewers: if opt.reviewers:
reviewers = _SplitEmails(opt.reviewers) reviewers = _SplitEmails(opt.reviewers)
if opt.cc: if opt.cc:
cc = _SplitEmails(opt.cc) cc = _SplitEmails(opt.cc)
people = (reviewers,cc) people = (reviewers, cc)
if not pending: 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: elif len(pending) == 1 and len(pending[0][1]) == 1:
self._SingleBranch(opt, pending[0][1][0], people) self._SingleBranch(opt, pending[0][1][0], people)
else: else:

View File

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

View File

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

View File

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