mirror of
https://gerrit.googlesource.com/git-repo
synced 2025-06-26 20:17:52 +00:00
Compare commits
29 Commits
Author | SHA1 | Date | |
---|---|---|---|
feb39d61ef | |||
7198572dd7 | |||
2daf66740b | |||
f4f04d9fa8 | |||
18afd7f679 | |||
6623b21e10 | |||
ca8c32cd7a | |||
f0a9a1a30e | |||
879a9a5cf0 | |||
ff6929dde8 | |||
1c85f4e43b | |||
719965af35 | |||
5732e47ebb | |||
f3fdf823cf | |||
a1bfd2cd72 | |||
6d7508b3d5 | |||
9452e4ec09 | |||
4c50deea28 | |||
d63060fc95 | |||
b6ea3bfcc3 | |||
aa4982e4c9 | |||
9bb1816bdc | |||
c24c720b61 | |||
2d1a396897 | |||
1dcb58a7d0 | |||
37dbf2bf0f | |||
438c54713a | |||
e020ebee4e | |||
21c5c34ee2 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -1 +1,2 @@
|
||||
*.pyc
|
||||
.repopickle_*
|
||||
|
80
SUBMITTING_PATCHES
Normal file
80
SUBMITTING_PATCHES
Normal file
@ -0,0 +1,80 @@
|
||||
Short Version:
|
||||
|
||||
- Make small logical changes.
|
||||
- Provide a meaningful commit message.
|
||||
- Make sure all code is under the Apache License, 2.0.
|
||||
- Publish your changes for review:
|
||||
|
||||
git push ssh://review.source.android.com:29418/tools/repo.git HEAD:refs/for/master
|
||||
|
||||
|
||||
Long Version:
|
||||
|
||||
I wanted a file describing how to submit patches for repo,
|
||||
so I started with the one found in the core Git distribution
|
||||
(Documentation/SubmittingPatches), which itself was based on the
|
||||
patch submission guidelines for the Linux kernel.
|
||||
|
||||
However there are some differences, so please review and familiarize
|
||||
yourself with the following relevant bits:
|
||||
|
||||
|
||||
(1) Make separate commits for logically separate changes.
|
||||
|
||||
Unless your patch is really trivial, you should not be sending
|
||||
out a patch that was generated between your working tree and your
|
||||
commit head. Instead, always make a commit with complete commit
|
||||
message and generate a series of patches from your repository.
|
||||
It is a good discipline.
|
||||
|
||||
Describe the technical detail of the change(s).
|
||||
|
||||
If your description starts to get too long, that's a sign that you
|
||||
probably need to split up your commit to finer grained pieces.
|
||||
|
||||
|
||||
(2) Check the license
|
||||
|
||||
repo is licensed under the Apache License, 2.0.
|
||||
|
||||
Because of this licensing model *every* file within the project
|
||||
*must* list the license that covers it in the header of the file.
|
||||
Any new contributions to an existing file *must* be submitted under
|
||||
the current license of that file. Any new files *must* clearly
|
||||
indicate which license they are provided under in the file header.
|
||||
|
||||
Please verify that you are legally allowed and willing to submit your
|
||||
changes under the license covering each file *prior* to submitting
|
||||
your patch. It is virtually impossible to remove a patch once it
|
||||
has been applied and pushed out.
|
||||
|
||||
|
||||
(3) Sending your patches.
|
||||
|
||||
Do not email your patches to anyone.
|
||||
|
||||
Instead, login to the Gerrit Code Review tool at:
|
||||
|
||||
https://review.source.android.com/
|
||||
|
||||
Ensure you have completed one of the necessary contributor
|
||||
agreements, providing documentation to the project maintainers that
|
||||
they have right to redistribute your work under the Apache License:
|
||||
|
||||
https://review.source.android.com/#settings,agreements
|
||||
|
||||
Ensure you have registered one or more SSH public keys, so you can
|
||||
push your commits directly over SSH:
|
||||
|
||||
https://review.source.android.com/#settings,ssh-keys
|
||||
|
||||
Push your patches over SSH to the review server, possibly through
|
||||
a remembered remote to make this easier in the future:
|
||||
|
||||
git config remote.review.url ssh://review.source.android.com:29418/tools/repo.git
|
||||
git config remote.review.push HEAD:refs/for/master
|
||||
|
||||
git push review
|
||||
|
||||
You will be automatically emailed a copy of your commits, and any
|
||||
comments made by the project maintainers.
|
@ -22,6 +22,7 @@ following DTD:
|
||||
<!DOCTYPE manifest [
|
||||
<!ELEMENT manifest (remote*,
|
||||
default?,
|
||||
manifest-server?,
|
||||
remove-project*,
|
||||
project*)>
|
||||
|
||||
@ -33,6 +34,9 @@ following DTD:
|
||||
<!ELEMENT default (EMPTY)>
|
||||
<!ATTLIST default remote IDREF #IMPLIED>
|
||||
<!ATTLIST default revision CDATA #IMPLIED>
|
||||
|
||||
<!ELEMENT manifest-server (EMPTY)>
|
||||
<!ATTLIST url CDATA #REQUIRED>
|
||||
|
||||
<!ELEMENT project (EMPTY)>
|
||||
<!ATTLIST project name CDATA #REQUIRED>
|
||||
@ -89,6 +93,27 @@ Attribute `revision`: Name of a Git branch (e.g. `master` or
|
||||
revision attribute will use this revision.
|
||||
|
||||
|
||||
Element manifest-server
|
||||
-----------------------
|
||||
|
||||
At most one manifest-server may be specified. The url attribute
|
||||
is used to specify the URL of a manifest server, which is an
|
||||
XML RPC service that will return a manifest in which each project
|
||||
is pegged to a known good revision for the current branch and
|
||||
target.
|
||||
|
||||
The manifest server should implement:
|
||||
|
||||
GetApprovedManifest(branch, target)
|
||||
|
||||
The target to use is defined by environment variables TARGET_PRODUCT
|
||||
and TARGET_BUILD_VARIANT. These variables are used to create a string
|
||||
of the form $TARGET_PRODUCT-$TARGET_BUILD_VARIANT, e.g. passion-userdebug.
|
||||
If one of those variables or both are not present, the program will call
|
||||
GetApprovedManifest without the target paramater and the manifest server
|
||||
should choose a reasonable default target.
|
||||
|
||||
|
||||
Element project
|
||||
---------------
|
||||
|
||||
|
27
editor.py
27
editor.py
@ -14,6 +14,7 @@
|
||||
# limitations under the License.
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import subprocess
|
||||
import tempfile
|
||||
@ -38,9 +39,10 @@ class Editor(object):
|
||||
if e:
|
||||
return e
|
||||
|
||||
e = cls.globalConfig.GetString('core.editor')
|
||||
if e:
|
||||
return e
|
||||
if cls.globalConfig:
|
||||
e = cls.globalConfig.GetString('core.editor')
|
||||
if e:
|
||||
return e
|
||||
|
||||
e = os.getenv('VISUAL')
|
||||
if e:
|
||||
@ -69,21 +71,32 @@ least one of these before using this command."""
|
||||
Returns:
|
||||
new value of edited text; None if editing did not succeed
|
||||
"""
|
||||
editor = cls._GetEditor().split()
|
||||
editor = cls._GetEditor()
|
||||
if editor == ':':
|
||||
return data
|
||||
|
||||
fd, path = tempfile.mkstemp()
|
||||
try:
|
||||
os.write(fd, data)
|
||||
os.close(fd)
|
||||
fd = None
|
||||
|
||||
if re.compile("^.*[$ \t'].*$").match(editor):
|
||||
args = [editor + ' "$@"']
|
||||
shell = True
|
||||
else:
|
||||
args = [editor]
|
||||
shell = False
|
||||
args.append(path)
|
||||
|
||||
try:
|
||||
rc = subprocess.Popen(editor + [path]).wait()
|
||||
rc = subprocess.Popen(args, shell=shell).wait()
|
||||
except OSError, e:
|
||||
raise EditorError('editor failed, %s: %s %s'
|
||||
% (str(e), cls._GetEditor(), path))
|
||||
% (str(e), editor, path))
|
||||
if rc != 0:
|
||||
raise EditorError('editor failed with exit status %d: %s %s'
|
||||
% (rc, cls._GetEditor(), path))
|
||||
% (rc, editor, path))
|
||||
|
||||
fd2 = open(path)
|
||||
try:
|
||||
|
@ -17,6 +17,7 @@ import os
|
||||
import sys
|
||||
import subprocess
|
||||
import tempfile
|
||||
from signal import SIGTERM
|
||||
from error import GitError
|
||||
from trace import REPO_TRACE, IsTrace, Trace
|
||||
|
||||
@ -29,8 +30,9 @@ LAST_CWD = None
|
||||
|
||||
_ssh_proxy_path = None
|
||||
_ssh_sock_path = None
|
||||
_ssh_clients = []
|
||||
|
||||
def _ssh_sock(create=True):
|
||||
def ssh_sock(create=True):
|
||||
global _ssh_sock_path
|
||||
if _ssh_sock_path is None:
|
||||
if not create:
|
||||
@ -51,6 +53,24 @@ def _ssh_proxy():
|
||||
'git_ssh')
|
||||
return _ssh_proxy_path
|
||||
|
||||
def _add_ssh_client(p):
|
||||
_ssh_clients.append(p)
|
||||
|
||||
def _remove_ssh_client(p):
|
||||
try:
|
||||
_ssh_clients.remove(p)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
def terminate_ssh_clients():
|
||||
global _ssh_clients
|
||||
for p in _ssh_clients:
|
||||
try:
|
||||
os.kill(p.pid, SIGTERM)
|
||||
p.wait()
|
||||
except OSError:
|
||||
pass
|
||||
_ssh_clients = []
|
||||
|
||||
class _GitCall(object):
|
||||
def version(self):
|
||||
@ -119,7 +139,7 @@ class GitCommand(object):
|
||||
if disable_editor:
|
||||
env['GIT_EDITOR'] = ':'
|
||||
if ssh_proxy:
|
||||
env['REPO_SSH_SOCK'] = _ssh_sock()
|
||||
env['REPO_SSH_SOCK'] = ssh_sock()
|
||||
env['GIT_SSH'] = _ssh_proxy()
|
||||
|
||||
if project:
|
||||
@ -188,6 +208,9 @@ class GitCommand(object):
|
||||
except Exception, e:
|
||||
raise GitError('%s: %s' % (command[1], e))
|
||||
|
||||
if ssh_proxy:
|
||||
_add_ssh_client(p)
|
||||
|
||||
self.process = p
|
||||
self.stdin = p.stdin
|
||||
|
||||
@ -210,4 +233,8 @@ class GitCommand(object):
|
||||
else:
|
||||
p.stderr = None
|
||||
|
||||
return self.process.wait()
|
||||
try:
|
||||
rc = p.wait()
|
||||
finally:
|
||||
_remove_ssh_client(p)
|
||||
return rc
|
||||
|
@ -23,7 +23,10 @@ from signal import SIGTERM
|
||||
from urllib2 import urlopen, HTTPError
|
||||
from error import GitError, UploadError
|
||||
from trace import Trace
|
||||
from git_command import GitCommand, _ssh_sock
|
||||
|
||||
from git_command import GitCommand
|
||||
from git_command import ssh_sock
|
||||
from git_command import terminate_ssh_clients
|
||||
|
||||
R_HEADS = 'refs/heads/'
|
||||
R_TAGS = 'refs/tags/'
|
||||
@ -259,21 +262,28 @@ class GitConfig(object):
|
||||
os.remove(self._pickle)
|
||||
|
||||
def _ReadGit(self):
|
||||
d = self._do('--null', '--list')
|
||||
c = {}
|
||||
while d:
|
||||
lf = d.index('\n')
|
||||
nul = d.index('\0', lf + 1)
|
||||
"""
|
||||
Read configuration data from git.
|
||||
|
||||
key = _key(d[0:lf])
|
||||
val = d[lf + 1:nul]
|
||||
This internal method populates the GitConfig cache.
|
||||
|
||||
"""
|
||||
c = {}
|
||||
d = self._do('--null', '--list')
|
||||
if d is None:
|
||||
return c
|
||||
for line in d.rstrip('\0').split('\0'):
|
||||
if '\n' in line:
|
||||
key, val = line.split('\n', 1)
|
||||
else:
|
||||
key = line
|
||||
val = None
|
||||
|
||||
if key in c:
|
||||
c[key].append(val)
|
||||
else:
|
||||
c[key] = [val]
|
||||
|
||||
d = d[nul + 1:]
|
||||
return c
|
||||
|
||||
def _do(self, *args):
|
||||
@ -349,10 +359,14 @@ class RefSpec(object):
|
||||
_ssh_cache = {}
|
||||
_ssh_master = True
|
||||
|
||||
def _open_ssh(host, port):
|
||||
def _open_ssh(host, port=None):
|
||||
global _ssh_master
|
||||
|
||||
key = '%s:%s' % (host, port)
|
||||
if port is not None:
|
||||
key = '%s:%s' % (host, port)
|
||||
else:
|
||||
key = host
|
||||
|
||||
if key in _ssh_cache:
|
||||
return True
|
||||
|
||||
@ -364,11 +378,14 @@ def _open_ssh(host, port):
|
||||
return False
|
||||
|
||||
command = ['ssh',
|
||||
'-o','ControlPath %s' % _ssh_sock(),
|
||||
'-p',str(port),
|
||||
'-o','ControlPath %s' % ssh_sock(),
|
||||
'-M',
|
||||
'-N',
|
||||
host]
|
||||
|
||||
if port is not None:
|
||||
command[3:3] = ['-p',str(port)]
|
||||
|
||||
try:
|
||||
Trace(': %s', ' '.join(command))
|
||||
p = subprocess.Popen(command)
|
||||
@ -384,6 +401,8 @@ def _open_ssh(host, port):
|
||||
return True
|
||||
|
||||
def close_ssh():
|
||||
terminate_ssh_clients()
|
||||
|
||||
for key,p in _ssh_cache.iteritems():
|
||||
try:
|
||||
os.kill(p.pid, SIGTERM)
|
||||
@ -392,7 +411,7 @@ def close_ssh():
|
||||
pass
|
||||
_ssh_cache.clear()
|
||||
|
||||
d = _ssh_sock(create=False)
|
||||
d = ssh_sock(create=False)
|
||||
if d:
|
||||
try:
|
||||
os.rmdir(os.path.dirname(d))
|
||||
@ -410,7 +429,7 @@ def _preconnect(url):
|
||||
if ':' in host:
|
||||
host, port = host.split(':')
|
||||
else:
|
||||
port = 22
|
||||
port = None
|
||||
if scheme in ('ssh', 'git+ssh', 'ssh+git'):
|
||||
return _open_ssh(host, port)
|
||||
return False
|
||||
@ -418,7 +437,7 @@ def _preconnect(url):
|
||||
m = URI_SCP.match(url)
|
||||
if m:
|
||||
host = m.group(1)
|
||||
return _open_ssh(host, 22)
|
||||
return _open_ssh(host)
|
||||
|
||||
return False
|
||||
|
||||
@ -435,8 +454,30 @@ class Remote(object):
|
||||
self._Get('fetch', all=True))
|
||||
self._review_protocol = None
|
||||
|
||||
def _InsteadOf(self):
|
||||
globCfg = GitConfig.ForUser()
|
||||
urlList = globCfg.GetSubSections('url')
|
||||
longest = ""
|
||||
longestUrl = ""
|
||||
|
||||
for url in urlList:
|
||||
key = "url." + url + ".insteadOf"
|
||||
insteadOfList = globCfg.GetString(key, all=True)
|
||||
|
||||
for insteadOf in insteadOfList:
|
||||
if self.url.startswith(insteadOf) \
|
||||
and len(insteadOf) > len(longest):
|
||||
longest = insteadOf
|
||||
longestUrl = url
|
||||
|
||||
if len(longest) == 0:
|
||||
return self.url
|
||||
|
||||
return self.url.replace(longest, longestUrl, 1)
|
||||
|
||||
def PreConnectFetch(self):
|
||||
return _preconnect(self.url)
|
||||
connectionUrl = self._InsteadOf()
|
||||
return _preconnect(connectionUrl)
|
||||
|
||||
@property
|
||||
def ReviewProtocol(self):
|
||||
|
101
hooks/commit-msg
Executable file
101
hooks/commit-msg
Executable file
@ -0,0 +1,101 @@
|
||||
#!/bin/sh
|
||||
# From Gerrit Code Review 2.1.2-rc2-33-g7e30c72
|
||||
#
|
||||
# Part of Gerrit Code Review (http://code.google.com/p/gerrit/)
|
||||
#
|
||||
# Copyright (C) 2009 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.
|
||||
#
|
||||
|
||||
CHANGE_ID_AFTER="Bug|Issue"
|
||||
MSG="$1"
|
||||
|
||||
# Check for, and add if missing, a unique Change-Id
|
||||
#
|
||||
add_ChangeId() {
|
||||
clean_message=$(sed -e '
|
||||
/^diff --git a\/.*/{
|
||||
s///
|
||||
q
|
||||
}
|
||||
/^Signed-off-by:/d
|
||||
/^#/d
|
||||
' "$MSG" | git stripspace)
|
||||
if test -z "$clean_message"
|
||||
then
|
||||
return
|
||||
fi
|
||||
|
||||
if grep -i '^Change-Id:' "$MSG" >/dev/null
|
||||
then
|
||||
return
|
||||
fi
|
||||
|
||||
id=$(_gen_ChangeId)
|
||||
perl -e '
|
||||
$MSG = shift;
|
||||
$id = shift;
|
||||
$CHANGE_ID_AFTER = shift;
|
||||
|
||||
undef $/;
|
||||
open(I, $MSG); $_ = <I>; close I;
|
||||
s|^diff --git a/.*||ms;
|
||||
s|^#.*$||mg;
|
||||
exit unless $_;
|
||||
|
||||
@message = split /\n/;
|
||||
$haveFooter = 0;
|
||||
$startFooter = @message;
|
||||
for($line = @message - 1; $line >= 0; $line--) {
|
||||
$_ = $message[$line];
|
||||
|
||||
($haveFooter++, next) if /^[a-zA-Z0-9-]+:/;
|
||||
next if /^[ []/;
|
||||
$startFooter = $line if ($haveFooter && /^\r?$/);
|
||||
last;
|
||||
}
|
||||
|
||||
@footer = @message[$startFooter+1..@message];
|
||||
@message = @message[0..$startFooter];
|
||||
push(@footer, "") unless @footer;
|
||||
|
||||
for ($line = 0; $line < @footer; $line++) {
|
||||
$_ = $footer[$line];
|
||||
next if /^($CHANGE_ID_AFTER):/i;
|
||||
last;
|
||||
}
|
||||
splice(@footer, $line, 0, "Change-Id: I$id");
|
||||
|
||||
$_ = join("\n", @message, @footer);
|
||||
open(O, ">$MSG"); print O; close O;
|
||||
' "$MSG" "$id" "$CHANGE_ID_AFTER"
|
||||
}
|
||||
_gen_ChangeIdInput() {
|
||||
echo "tree $(git write-tree)"
|
||||
if parent=$(git rev-parse HEAD^0 2>/dev/null)
|
||||
then
|
||||
echo "parent $parent"
|
||||
fi
|
||||
echo "author $(git var GIT_AUTHOR_IDENT)"
|
||||
echo "committer $(git var GIT_COMMITTER_IDENT)"
|
||||
echo
|
||||
printf '%s' "$clean_message"
|
||||
}
|
||||
_gen_ChangeId() {
|
||||
_gen_ChangeIdInput |
|
||||
git hash-object -t commit --stdin
|
||||
}
|
||||
|
||||
|
||||
add_ChangeId
|
@ -65,8 +65,8 @@ class XmlManifest(object):
|
||||
|
||||
self._Unload()
|
||||
|
||||
def Link(self, name):
|
||||
"""Update the repo metadata to use a different manifest.
|
||||
def Override(self, name):
|
||||
"""Use a different manifest, just for the current instantiation.
|
||||
"""
|
||||
path = os.path.join(self.manifestProject.worktree, name)
|
||||
if not os.path.isfile(path):
|
||||
@ -80,6 +80,11 @@ class XmlManifest(object):
|
||||
finally:
|
||||
self.manifestFile = old
|
||||
|
||||
def Link(self, name):
|
||||
"""Update the repo metadata to use a different manifest.
|
||||
"""
|
||||
self.Override(name)
|
||||
|
||||
try:
|
||||
if os.path.exists(self.manifestFile):
|
||||
os.remove(self.manifestFile)
|
||||
@ -123,6 +128,12 @@ class XmlManifest(object):
|
||||
root.appendChild(e)
|
||||
root.appendChild(doc.createTextNode(''))
|
||||
|
||||
if self._manifest_server:
|
||||
e = doc.createElement('manifest-server')
|
||||
e.setAttribute('url', self._manifest_server)
|
||||
root.appendChild(e)
|
||||
root.appendChild(doc.createTextNode(''))
|
||||
|
||||
sort_projects = list(self.projects.keys())
|
||||
sort_projects.sort()
|
||||
|
||||
@ -168,6 +179,11 @@ class XmlManifest(object):
|
||||
self._Load()
|
||||
return self._default
|
||||
|
||||
@property
|
||||
def manifest_server(self):
|
||||
self._Load()
|
||||
return self._manifest_server
|
||||
|
||||
@property
|
||||
def IsMirror(self):
|
||||
return self.manifestProject.config.GetBoolean('repo.mirror')
|
||||
@ -178,12 +194,13 @@ class XmlManifest(object):
|
||||
self._remotes = {}
|
||||
self._default = None
|
||||
self.branch = None
|
||||
self._manifest_server = None
|
||||
|
||||
def _Load(self):
|
||||
if not self._loaded:
|
||||
m = self.manifestProject
|
||||
b = m.GetBranch(m.CurrentBranch).merge
|
||||
if b.startswith(R_HEADS):
|
||||
if b is not None and b.startswith(R_HEADS):
|
||||
b = b[len(R_HEADS):]
|
||||
self.branch = b
|
||||
|
||||
@ -246,6 +263,15 @@ class XmlManifest(object):
|
||||
if self._default is None:
|
||||
self._default = _Default()
|
||||
|
||||
for node in config.childNodes:
|
||||
if node.nodeName == 'manifest-server':
|
||||
url = self._reqatt(node, 'url')
|
||||
if self._manifest_server is not None:
|
||||
raise ManifestParseError, \
|
||||
'duplicate manifest-server in %s' % \
|
||||
(self.manifestFile)
|
||||
self._manifest_server = url
|
||||
|
||||
for node in config.childNodes:
|
||||
if node.nodeName == 'project':
|
||||
project = self._ParseProject(node)
|
||||
@ -315,7 +341,7 @@ class XmlManifest(object):
|
||||
def _ParseProject(self, node):
|
||||
"""
|
||||
reads a <project> element from the manifest file
|
||||
"""
|
||||
"""
|
||||
name = self._reqatt(node, 'name')
|
||||
|
||||
remote = self._get_remote(node)
|
||||
|
@ -13,10 +13,13 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import os
|
||||
import sys
|
||||
from time import time
|
||||
from trace import IsTrace
|
||||
|
||||
_NOT_TTY = not os.isatty(2)
|
||||
|
||||
class Progress(object):
|
||||
def __init__(self, title, total=0):
|
||||
self._title = title
|
||||
@ -29,7 +32,7 @@ class Progress(object):
|
||||
def update(self, inc=1):
|
||||
self._done += inc
|
||||
|
||||
if IsTrace():
|
||||
if _NOT_TTY or IsTrace():
|
||||
return
|
||||
|
||||
if not self._show:
|
||||
@ -56,7 +59,7 @@ class Progress(object):
|
||||
sys.stderr.flush()
|
||||
|
||||
def end(self):
|
||||
if IsTrace() or not self._show:
|
||||
if _NOT_TTY or IsTrace() or not self._show:
|
||||
return
|
||||
|
||||
if self._total <= 0:
|
||||
|
40
project.py
40
project.py
@ -203,6 +203,10 @@ class _CopyFile:
|
||||
# remove existing file first, since it might be read-only
|
||||
if os.path.exists(dest):
|
||||
os.remove(dest)
|
||||
else:
|
||||
dir = os.path.dirname(dest)
|
||||
if not os.path.isdir(dir):
|
||||
os.makedirs(dir)
|
||||
shutil.copy(src, dest)
|
||||
# make the file read-only
|
||||
mode = os.stat(dest)[stat.ST_MODE]
|
||||
@ -706,10 +710,9 @@ class Project(object):
|
||||
# commits are not yet merged upstream. We do not want
|
||||
# to rewrite the published commits so we punt.
|
||||
#
|
||||
syncbuf.info(self,
|
||||
"branch %s is published but is now %d commits behind",
|
||||
branch.name,
|
||||
len(upstream_gain))
|
||||
syncbuf.fail(self,
|
||||
"branch %s is published (but not merged) and is now %d commits behind"
|
||||
% (branch.name, len(upstream_gain)))
|
||||
return
|
||||
elif pub == head:
|
||||
# All published commits are merged, and thus we are a
|
||||
@ -728,7 +731,7 @@ class Project(object):
|
||||
last_mine = None
|
||||
cnt_mine = 0
|
||||
for commit in local_changes:
|
||||
commit_id, committer_email = commit.split(' ', 2)
|
||||
commit_id, committer_email = commit.split(' ', 1)
|
||||
if committer_email == self.UserEmail:
|
||||
last_mine = commit_id
|
||||
cnt_mine += 1
|
||||
@ -1056,13 +1059,27 @@ class Project(object):
|
||||
if not os.path.exists(hooks):
|
||||
os.makedirs(hooks)
|
||||
for stock_hook in repo_hooks():
|
||||
dst = os.path.join(hooks, os.path.basename(stock_hook))
|
||||
name = os.path.basename(stock_hook)
|
||||
|
||||
if name in ('commit-msg') and not self.remote.review:
|
||||
# Don't install a Gerrit Code Review hook if this
|
||||
# project does not appear to use it for reviews.
|
||||
#
|
||||
continue
|
||||
|
||||
dst = os.path.join(hooks, name)
|
||||
if os.path.islink(dst):
|
||||
continue
|
||||
if os.path.exists(dst):
|
||||
if filecmp.cmp(stock_hook, dst, shallow=False):
|
||||
os.remove(dst)
|
||||
else:
|
||||
_error("%s: Not replacing %s hook", self.relpath, name)
|
||||
continue
|
||||
try:
|
||||
os.symlink(relpath(stock_hook, dst), dst)
|
||||
except OSError, e:
|
||||
if e.errno == errno.EEXIST:
|
||||
pass
|
||||
elif e.errno == errno.EPERM:
|
||||
if e.errno == errno.EPERM:
|
||||
raise GitError('filesystem must support symlinks')
|
||||
else:
|
||||
raise
|
||||
@ -1120,7 +1137,10 @@ class Project(object):
|
||||
try:
|
||||
src = os.path.join(self.gitdir, name)
|
||||
dst = os.path.join(dotgit, name)
|
||||
os.symlink(relpath(src, dst), dst)
|
||||
if os.path.islink(dst) or not os.path.exists(dst):
|
||||
os.symlink(relpath(src, dst), dst)
|
||||
else:
|
||||
raise GitError('cannot overwrite a local work tree')
|
||||
except OSError, e:
|
||||
if e.errno == errno.EPERM:
|
||||
raise GitError('filesystem must support symlinks')
|
||||
|
@ -136,7 +136,7 @@ is shown, then the branch appears in all projects.
|
||||
hdr('%c%c %-*s' % (current, published, width, name))
|
||||
out.write(' |')
|
||||
|
||||
if in_cnt < project_cnt and (in_cnt == 1):
|
||||
if in_cnt < project_cnt:
|
||||
fmt = out.write
|
||||
paths = []
|
||||
if in_cnt < project_cnt - in_cnt:
|
||||
@ -150,15 +150,17 @@ is shown, then the branch appears in all projects.
|
||||
for b in i.projects:
|
||||
have.add(b.project)
|
||||
for p in projects:
|
||||
paths.append(p.relpath)
|
||||
if not p in have:
|
||||
paths.append(p.relpath)
|
||||
|
||||
s = ' %s %s' % (type, ', '.join(paths))
|
||||
if width + 7 + len(s) < 80:
|
||||
fmt(s)
|
||||
else:
|
||||
out.nl()
|
||||
fmt(' %s:' % type)
|
||||
fmt(' %s:' % type)
|
||||
for p in paths:
|
||||
out.nl()
|
||||
fmt(' %s' % p)
|
||||
fmt(width*' ' + ' %s' % p)
|
||||
else:
|
||||
out.write(' in all projects')
|
||||
out.nl()
|
||||
|
@ -204,7 +204,7 @@ contain a line that matches both expressions:
|
||||
else:
|
||||
out.project('--- project %s ---' % project.relpath)
|
||||
out.nl()
|
||||
out.write(p.stderr)
|
||||
out.write("%s", p.stderr)
|
||||
out.nl()
|
||||
continue
|
||||
have_match = True
|
||||
@ -217,17 +217,17 @@ contain a line that matches both expressions:
|
||||
if have_rev and full_name:
|
||||
for line in r:
|
||||
rev, line = line.split(':', 1)
|
||||
out.write(rev)
|
||||
out.write("%s", rev)
|
||||
out.write(':')
|
||||
out.project(project.relpath)
|
||||
out.write('/')
|
||||
out.write(line)
|
||||
out.write("%s", line)
|
||||
out.nl()
|
||||
elif full_name:
|
||||
for line in r:
|
||||
out.project(project.relpath)
|
||||
out.write('/')
|
||||
out.write(line)
|
||||
out.write("%s", line)
|
||||
out.nl()
|
||||
else:
|
||||
for line in r:
|
||||
|
@ -148,20 +148,34 @@ to update the working directory files.
|
||||
print >>sys.stderr, 'fatal: %s' % str(e)
|
||||
sys.exit(1)
|
||||
|
||||
def _PromptKey(self, prompt, key, value):
|
||||
def _Prompt(self, prompt, value):
|
||||
mp = self.manifest.manifestProject
|
||||
|
||||
sys.stdout.write('%-10s [%s]: ' % (prompt, value))
|
||||
a = sys.stdin.readline().strip()
|
||||
if a != '' and a != value:
|
||||
mp.config.SetString(key, a)
|
||||
if a == '':
|
||||
return value
|
||||
return a
|
||||
|
||||
def _ConfigureUser(self):
|
||||
mp = self.manifest.manifestProject
|
||||
|
||||
print ''
|
||||
self._PromptKey('Your Name', 'user.name', mp.UserName)
|
||||
self._PromptKey('Your Email', 'user.email', mp.UserEmail)
|
||||
while True:
|
||||
print ''
|
||||
name = self._Prompt('Your Name', mp.UserName)
|
||||
email = self._Prompt('Your Email', mp.UserEmail)
|
||||
|
||||
print ''
|
||||
print 'Your identity is: %s <%s>' % (name, email)
|
||||
sys.stdout.write('is this correct [y/n]? ')
|
||||
a = sys.stdin.readline().strip()
|
||||
if a in ('yes', 'y', 't', 'true'):
|
||||
break
|
||||
|
||||
if name != mp.UserName:
|
||||
mp.config.SetString('user.name', name)
|
||||
if email != mp.UserEmail:
|
||||
mp.config.SetString('user.email', email)
|
||||
|
||||
def _HasColorSet(self, gc):
|
||||
for n in ['ui', 'diff', 'status']:
|
||||
|
33
subcmds/smartsync.py
Normal file
33
subcmds/smartsync.py
Normal file
@ -0,0 +1,33 @@
|
||||
#
|
||||
# Copyright (C) 2010 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 sync import Sync
|
||||
|
||||
class Smartsync(Sync):
|
||||
common = True
|
||||
helpSummary = "Update working tree to the latest known good revision"
|
||||
helpUsage = """
|
||||
%prog [<project>...]
|
||||
"""
|
||||
helpDescription = """
|
||||
The '%prog' command is a shortcut for sync -s.
|
||||
"""
|
||||
|
||||
def _Options(self, p):
|
||||
Sync._Options(self, p, show_smart=False)
|
||||
|
||||
def Execute(self, opt, args):
|
||||
opt.smart_sync = True
|
||||
Sync.Execute(self, opt, args)
|
181
subcmds/sync.py
181
subcmds/sync.py
@ -17,11 +17,19 @@ from optparse import SUPPRESS_HELP
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import socket
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
import xmlrpclib
|
||||
|
||||
try:
|
||||
import threading as _threading
|
||||
except ImportError:
|
||||
import dummy_threading as _threading
|
||||
|
||||
from git_command import GIT
|
||||
from git_refs import R_HEADS
|
||||
from project import HEAD
|
||||
from project import Project
|
||||
from project import RemoteSpec
|
||||
@ -32,6 +40,7 @@ from project import SyncBuffer
|
||||
from progress import Progress
|
||||
|
||||
class Sync(Command, MirrorSafeCommand):
|
||||
jobs = 1
|
||||
common = True
|
||||
helpSummary = "Update working tree to the latest revision"
|
||||
helpUsage = """
|
||||
@ -57,6 +66,10 @@ back to the manifest revision. This option is especially helpful
|
||||
if the project is currently on a topic branch, but the manifest
|
||||
revision is temporarily needed.
|
||||
|
||||
The -s/--smart-sync option can be used to sync to a known good
|
||||
build as specified by the manifest-server element in the current
|
||||
manifest.
|
||||
|
||||
SSH Connections
|
||||
---------------
|
||||
|
||||
@ -87,7 +100,7 @@ later is required to fix a server side protocol bug.
|
||||
|
||||
"""
|
||||
|
||||
def _Options(self, p):
|
||||
def _Options(self, p, show_smart=True):
|
||||
p.add_option('-l','--local-only',
|
||||
dest='local_only', action='store_true',
|
||||
help="only update working tree, don't fetch")
|
||||
@ -97,6 +110,13 @@ later is required to fix a server side protocol bug.
|
||||
p.add_option('-d','--detach',
|
||||
dest='detach_head', action='store_true',
|
||||
help='detach projects back to manifest revision')
|
||||
p.add_option('-j','--jobs',
|
||||
dest='jobs', action='store', type='int',
|
||||
help="number of projects to fetch simultaneously")
|
||||
if show_smart:
|
||||
p.add_option('-s', '--smart-sync',
|
||||
dest='smart_sync', action='store_true',
|
||||
help='smart sync using manifest from a known good build')
|
||||
|
||||
g = p.add_option_group('repo Version options')
|
||||
g.add_option('--no-repo-verify',
|
||||
@ -106,17 +126,44 @@ later is required to fix a server side protocol bug.
|
||||
dest='repo_upgraded', action='store_true',
|
||||
help=SUPPRESS_HELP)
|
||||
|
||||
def _FetchHelper(self, project, lock, fetched, pm, sem):
|
||||
if not project.Sync_NetworkHalf():
|
||||
print >>sys.stderr, 'error: Cannot fetch %s' % project.name
|
||||
sem.release()
|
||||
sys.exit(1)
|
||||
|
||||
lock.acquire()
|
||||
fetched.add(project.gitdir)
|
||||
pm.update()
|
||||
lock.release()
|
||||
sem.release()
|
||||
|
||||
def _Fetch(self, projects):
|
||||
fetched = set()
|
||||
pm = Progress('Fetching projects', len(projects))
|
||||
for project in projects:
|
||||
pm.update()
|
||||
|
||||
if project.Sync_NetworkHalf():
|
||||
fetched.add(project.gitdir)
|
||||
else:
|
||||
print >>sys.stderr, 'error: Cannot fetch %s' % project.name
|
||||
sys.exit(1)
|
||||
if self.jobs == 1:
|
||||
for project in projects:
|
||||
pm.update()
|
||||
if project.Sync_NetworkHalf():
|
||||
fetched.add(project.gitdir)
|
||||
else:
|
||||
print >>sys.stderr, 'error: Cannot fetch %s' % project.name
|
||||
sys.exit(1)
|
||||
else:
|
||||
threads = set()
|
||||
lock = _threading.Lock()
|
||||
sem = _threading.Semaphore(self.jobs)
|
||||
for project in projects:
|
||||
sem.acquire()
|
||||
t = _threading.Thread(target = self._FetchHelper,
|
||||
args = (project, lock, fetched, pm, sem))
|
||||
threads.add(t)
|
||||
t.start()
|
||||
|
||||
for t in threads:
|
||||
t.join()
|
||||
|
||||
pm.end()
|
||||
return fetched
|
||||
|
||||
@ -139,32 +186,36 @@ later is required to fix a server side protocol bug.
|
||||
if not path:
|
||||
continue
|
||||
if path not in new_project_paths:
|
||||
project = Project(
|
||||
manifest = self.manifest,
|
||||
name = path,
|
||||
remote = RemoteSpec('origin'),
|
||||
gitdir = os.path.join(self.manifest.topdir,
|
||||
path, '.git'),
|
||||
worktree = os.path.join(self.manifest.topdir, path),
|
||||
relpath = path,
|
||||
revisionExpr = 'HEAD',
|
||||
revisionId = None)
|
||||
if project.IsDirty():
|
||||
print >>sys.stderr, 'error: Cannot remove project "%s": \
|
||||
"""If the path has already been deleted, we don't need to do it
|
||||
"""
|
||||
if os.path.exists(self.manifest.topdir + '/' + path):
|
||||
project = Project(
|
||||
manifest = self.manifest,
|
||||
name = path,
|
||||
remote = RemoteSpec('origin'),
|
||||
gitdir = os.path.join(self.manifest.topdir,
|
||||
path, '.git'),
|
||||
worktree = os.path.join(self.manifest.topdir, path),
|
||||
relpath = path,
|
||||
revisionExpr = 'HEAD',
|
||||
revisionId = None)
|
||||
|
||||
if project.IsDirty():
|
||||
print >>sys.stderr, 'error: Cannot remove project "%s": \
|
||||
uncommitted changes are present' % project.relpath
|
||||
print >>sys.stderr, ' commit changes, then run sync again'
|
||||
return -1
|
||||
else:
|
||||
print >>sys.stderr, 'Deleting obsolete path %s' % project.worktree
|
||||
shutil.rmtree(project.worktree)
|
||||
# Try deleting parent subdirs if they are empty
|
||||
dir = os.path.dirname(project.worktree)
|
||||
while dir != self.manifest.topdir:
|
||||
try:
|
||||
os.rmdir(dir)
|
||||
except OSError:
|
||||
break
|
||||
dir = os.path.dirname(dir)
|
||||
print >>sys.stderr, ' commit changes, then run sync again'
|
||||
return -1
|
||||
else:
|
||||
print >>sys.stderr, 'Deleting obsolete path %s' % project.worktree
|
||||
shutil.rmtree(project.worktree)
|
||||
# Try deleting parent subdirs if they are empty
|
||||
dir = os.path.dirname(project.worktree)
|
||||
while dir != self.manifest.topdir:
|
||||
try:
|
||||
os.rmdir(dir)
|
||||
except OSError:
|
||||
break
|
||||
dir = os.path.dirname(dir)
|
||||
|
||||
new_project_paths.sort()
|
||||
fd = open(file_path, 'w')
|
||||
@ -176,6 +227,8 @@ uncommitted changes are present' % project.relpath
|
||||
return 0
|
||||
|
||||
def Execute(self, opt, args):
|
||||
if opt.jobs:
|
||||
self.jobs = opt.jobs
|
||||
if opt.network_only and opt.detach_head:
|
||||
print >>sys.stderr, 'error: cannot combine -n and -d'
|
||||
sys.exit(1)
|
||||
@ -183,6 +236,51 @@ uncommitted changes are present' % project.relpath
|
||||
print >>sys.stderr, 'error: cannot combine -n and -l'
|
||||
sys.exit(1)
|
||||
|
||||
if opt.smart_sync:
|
||||
if not self.manifest.manifest_server:
|
||||
print >>sys.stderr, \
|
||||
'error: cannot smart sync: no manifest server defined in manifest'
|
||||
sys.exit(1)
|
||||
try:
|
||||
server = xmlrpclib.Server(self.manifest.manifest_server)
|
||||
p = self.manifest.manifestProject
|
||||
b = p.GetBranch(p.CurrentBranch)
|
||||
branch = b.merge
|
||||
if branch.startswith(R_HEADS):
|
||||
branch = branch[len(R_HEADS):]
|
||||
|
||||
env = dict(os.environ)
|
||||
if (env.has_key('TARGET_PRODUCT') and
|
||||
env.has_key('TARGET_BUILD_VARIANT')):
|
||||
target = '%s-%s' % (env['TARGET_PRODUCT'],
|
||||
env['TARGET_BUILD_VARIANT'])
|
||||
[success, manifest_str] = server.GetApprovedManifest(branch, target)
|
||||
else:
|
||||
[success, manifest_str] = server.GetApprovedManifest(branch)
|
||||
|
||||
if success:
|
||||
manifest_name = "smart_sync_override.xml"
|
||||
manifest_path = os.path.join(self.manifest.manifestProject.worktree,
|
||||
manifest_name)
|
||||
try:
|
||||
f = open(manifest_path, 'w')
|
||||
try:
|
||||
f.write(manifest_str)
|
||||
finally:
|
||||
f.close()
|
||||
except IOError:
|
||||
print >>sys.stderr, 'error: cannot write manifest to %s' % \
|
||||
manifest_path
|
||||
sys.exit(1)
|
||||
self.manifest.Override(manifest_name)
|
||||
else:
|
||||
print >>sys.stderr, 'error: %s' % manifest_str
|
||||
sys.exit(1)
|
||||
except socket.error:
|
||||
print >>sys.stderr, 'error: cannot connect to manifest server %s' % (
|
||||
self.manifest.manifest_server)
|
||||
sys.exit(1)
|
||||
|
||||
rp = self.manifest.repoProject
|
||||
rp.PreSync()
|
||||
|
||||
@ -192,6 +290,15 @@ uncommitted changes are present' % project.relpath
|
||||
if opt.repo_upgraded:
|
||||
_PostRepoUpgrade(self.manifest)
|
||||
|
||||
if not opt.local_only:
|
||||
mp.Sync_NetworkHalf()
|
||||
|
||||
if mp.HasChanges:
|
||||
syncbuf = SyncBuffer(mp.config)
|
||||
mp.Sync_LocalHalf(syncbuf)
|
||||
if not syncbuf.Finish():
|
||||
sys.exit(1)
|
||||
self.manifest._Unload()
|
||||
all = self.GetProjects(args, missing_ok=True)
|
||||
|
||||
if not opt.local_only:
|
||||
@ -199,7 +306,6 @@ uncommitted changes are present' % project.relpath
|
||||
now = time.time()
|
||||
if (24 * 60 * 60) <= (now - rp.LastFetch):
|
||||
to_fetch.append(rp)
|
||||
to_fetch.append(mp)
|
||||
to_fetch.extend(all)
|
||||
|
||||
fetched = self._Fetch(to_fetch)
|
||||
@ -208,12 +314,6 @@ uncommitted changes are present' % project.relpath
|
||||
# bail out now; the rest touches the working tree
|
||||
return
|
||||
|
||||
if mp.HasChanges:
|
||||
syncbuf = SyncBuffer(mp.config)
|
||||
mp.Sync_LocalHalf(syncbuf)
|
||||
if not syncbuf.Finish():
|
||||
sys.exit(1)
|
||||
|
||||
self.manifest._Unload()
|
||||
all = self.GetProjects(args, missing_ok=True)
|
||||
missing = []
|
||||
@ -241,7 +341,6 @@ uncommitted changes are present' % project.relpath
|
||||
if not syncbuf.Finish():
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def _PostRepoUpgrade(manifest):
|
||||
for project in manifest.projects.values():
|
||||
if project.Exists:
|
||||
|
@ -20,6 +20,17 @@ from command import InteractiveCommand
|
||||
from editor import Editor
|
||||
from error import UploadError
|
||||
|
||||
UNUSUAL_COMMIT_THRESHOLD = 5
|
||||
|
||||
def _ConfirmManyUploads(multiple_branches=False):
|
||||
if multiple_branches:
|
||||
print "ATTENTION: One or more branches has an unusually high number of commits."
|
||||
else:
|
||||
print "ATTENTION: You are uploading an unusually high number of commits."
|
||||
print "YOU PROBABLY DO NOT MEAN TO DO THIS. (Did you rebase across branches?)"
|
||||
answer = raw_input("If you are sure you intend to do this, type 'yes': ").strip()
|
||||
return answer == "yes"
|
||||
|
||||
def _die(fmt, *args):
|
||||
msg = fmt % args
|
||||
print >>sys.stderr, 'error: %s' % msg
|
||||
@ -128,6 +139,10 @@ Gerrit Code Review: http://code.google.com/p/gerrit/
|
||||
answer = sys.stdin.readline().strip()
|
||||
answer = answer in ('y', 'Y', 'yes', '1', 'true', 't')
|
||||
|
||||
if answer:
|
||||
if len(branch.commits) > UNUSUAL_COMMIT_THRESHOLD:
|
||||
answer = _ConfirmManyUploads()
|
||||
|
||||
if answer:
|
||||
self._UploadAndReport([branch], people)
|
||||
else:
|
||||
@ -192,6 +207,16 @@ Gerrit Code Review: http://code.google.com/p/gerrit/
|
||||
todo.append(branch)
|
||||
if not todo:
|
||||
_die("nothing uncommented for upload")
|
||||
|
||||
many_commits = False
|
||||
for branch in todo:
|
||||
if len(branch.commits) > UNUSUAL_COMMIT_THRESHOLD:
|
||||
many_commits = True
|
||||
break
|
||||
if many_commits:
|
||||
if not _ConfirmManyUploads(multiple_branches=True):
|
||||
_die("upload aborted by user")
|
||||
|
||||
self._UploadAndReport(todo, people)
|
||||
|
||||
def _FindGerritChange(self, branch):
|
||||
@ -258,6 +283,10 @@ Gerrit Code Review: http://code.google.com/p/gerrit/
|
||||
print >>sys.stderr, " use 'repo upload' without --replace"
|
||||
sys.exit(1)
|
||||
|
||||
if len(branch.commits) > UNUSUAL_COMMIT_THRESHOLD:
|
||||
if not _ConfirmManyUploads(multiple_branches=True):
|
||||
_die("upload aborted by user")
|
||||
|
||||
branch.replace_changes = to_replace
|
||||
self._UploadAndReport([branch], people)
|
||||
|
||||
|
3
tests/fixtures/test.gitconfig
vendored
Normal file
3
tests/fixtures/test.gitconfig
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
[section]
|
||||
empty
|
||||
nonempty = true
|
52
tests/test_git_config.py
Normal file
52
tests/test_git_config.py
Normal file
@ -0,0 +1,52 @@
|
||||
import os
|
||||
import unittest
|
||||
|
||||
import git_config
|
||||
|
||||
def fixture(*paths):
|
||||
"""Return a path relative to test/fixtures.
|
||||
"""
|
||||
return os.path.join(os.path.dirname(__file__), 'fixtures', *paths)
|
||||
|
||||
class GitConfigUnitTest(unittest.TestCase):
|
||||
"""Tests the GitConfig class.
|
||||
"""
|
||||
def setUp(self):
|
||||
"""Create a GitConfig object using the test.gitconfig fixture.
|
||||
"""
|
||||
config_fixture = fixture('test.gitconfig')
|
||||
self.config = git_config.GitConfig(config_fixture)
|
||||
|
||||
def test_GetString_with_empty_config_values(self):
|
||||
"""
|
||||
Test config entries with no value.
|
||||
|
||||
[section]
|
||||
empty
|
||||
|
||||
"""
|
||||
val = self.config.GetString('section.empty')
|
||||
self.assertEqual(val, None)
|
||||
|
||||
def test_GetString_with_true_value(self):
|
||||
"""
|
||||
Test config entries with a string value.
|
||||
|
||||
[section]
|
||||
nonempty = true
|
||||
|
||||
"""
|
||||
val = self.config.GetString('section.nonempty')
|
||||
self.assertEqual(val, 'true')
|
||||
|
||||
def test_GetString_from_missing_file(self):
|
||||
"""
|
||||
Test missing config file
|
||||
"""
|
||||
config_fixture = fixture('not.present.gitconfig')
|
||||
config = git_config.GitConfig(config_fixture)
|
||||
val = config.GetString('empty')
|
||||
self.assertEqual(val, None)
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
Reference in New Issue
Block a user