Compare commits

...

11 Commits

Author SHA1 Message Date
d63060fc95 Check that we are not overwriting a local repository when syncing.
If a local git repository exists within the same folder as a new project that
is added, when the user syncs the repo, the sync will overwrite the local
files under the project's .git repository with its own symlinks. Make sure
that we do not overwrite 'normal' files in repo and throw an error when
that happens.
2010-01-20 10:27:50 -08:00
b6ea3bfcc3 Honor url.insteadOf when setting up SSH control master connection
Repo can now properly handle url.insteadOf sections in the
user's ~/.gitconfig file.  This means that a user can now enjoy
the master-ssh functionality even if he/she uses insteadOf's in
~/.gitconfig to rewrite git:// URLs to ssh:// style URLs.

Change-Id: Ic0f04a9c57206a7b89eb0f10bf188c4c483debe3
Signed-off-by: Shawn O. Pearce <sop@google.com>
2010-01-04 05:38:39 -08:00
aa4982e4c9 sync: Fix split call on malformed email addresses
If an email address in a commit object contains a space, like a few
malformed ones on the Linux kernel, we still want to split only on
the first space.

Unfortunately my brain was too damaged by Perl and originally wrote
the split asking for 2 results; in Python split's argument is how
many splits to perform.  Here we want only 1 split, to break apart
the commit identity from the email address on the same line.

Signed-off-by: Shawn O. Pearce <sop@google.com>
2009-12-30 18:38:27 -08:00
9bb1816bdc Fixing project renaming bug.
This bug happens when a project gets added to the manifest, and
then is renamed. Users who happened to have run "repo sync" after
the project was added but before the rename happened will try to
read the data from the old project, as the manifest was only updated
after all projects were updated successfully.
2009-12-10 15:24:45 -08:00
c24c720b61 Fix error parsing a non-existant configuration file
If a file (e.g. ~/.gitconfig) does not exist, we get None
here rather than a string.  NoneType lacks rstrip() so we
cannot strip it.

Signed-off-by: Shawn O. Pearce <sop@google.com>
2009-07-02 16:12:57 -07:00
2d1a396897 Document how to contribute to the repo project
Signed-off-by: Shawn O. Pearce <sop@google.com>
2009-07-02 13:18:55 -07:00
1dcb58a7d0 Support GIT_EDITOR='vim -c "set textwidth=80"'
If there are shell special characters in the editor string, we must
use /bin/sh to parse and execute it, rather than trying to rely on
a simple split(' ').  This avoids vim starting up with two empty
buffers, due to a misparsed command line.

Signed-off-by: Shawn O. Pearce <sop@google.com>
2009-07-02 12:45:47 -07:00
37dbf2bf0f Try to prevent 'repo sync' as a user name
When someone copies and pastes a setup line from a web page,
they might actually copy 'repo sync' onto the clipboard and wind
up pasting it into the "Your Name" prompt.  This means they will
initialize their client with the user name of "repo sync", creating
some rather funny looking commits later on.  For example:

  To setup your source tree:

    mkdir ~/code
    cd ~/code
    repo init -u git://....
    repo sync

If this entire block was just blindly copy and pasted into the
terminal, the shell won't read "repo sync" but "repo init" will.

By showing the user their full identity string, and asking them
to confirm it before we continue, we can give the hapless user a
chance to recover from this mistake, without unfairly harming those
who were actually named 'repo' by their parents.

Signed-off-by: Shawn O. Pearce <sop@google.com>
2009-07-02 10:53:04 -07:00
438c54713a git_config: handle configuration entries with no values
A git-config entry with no value was preventing repo
from initializing.  This modifies _ReadGit() to handle
config entries with empty values.

Signed-off-by: David Aguilar <davvid@gmail.com>
Reported-by: Josh Guilfoyle <jasta00@gmail.com>
2009-06-29 00:24:36 -07:00
e020ebee4e .gitignore: add an entry for repopickles
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-06-28 15:08:56 -07:00
21c5c34ee2 Support detached HEAD in manifest repository
If the manifest repository is on a detached HEAD and we are parsing
an XML formatted manifest we should simply set the branch property
to None, rather than crash with an AttributeError.

Signed-off-by: Shawn O. Pearce <sop@google.com>
2009-06-25 16:47:30 -07:00
10 changed files with 228 additions and 34 deletions

1
.gitignore vendored
View File

@ -1 +1,2 @@
*.pyc
.repopickle_*

80
SUBMITTING_PATCHES Normal file
View 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.

View File

@ -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:

View File

@ -259,21 +259,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):
@ -435,8 +442,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):

View File

@ -183,7 +183,7 @@ class XmlManifest(object):
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

View File

@ -728,7 +728,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
@ -1120,7 +1120,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')

View File

@ -148,20 +148,33 @@ 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 [yes/no]? ')
if 'yes' == sys.stdin.readline().strip():
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']:

View File

@ -111,7 +111,6 @@ later is required to fix a server side protocol bug.
pm = Progress('Fetching projects', len(projects))
for project in projects:
pm.update()
if project.Sync_NetworkHalf():
fetched.add(project.gitdir)
else:
@ -192,6 +191,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 +207,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 +215,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 +242,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:

3
tests/fixtures/test.gitconfig vendored Normal file
View File

@ -0,0 +1,3 @@
[section]
empty
nonempty = true

52
tests/test_git_config.py Normal file
View 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()