mirror of
https://gerrit.googlesource.com/git-repo
synced 2025-07-04 20:17:16 +00:00
Compare commits
9 Commits
Author | SHA1 | Date | |
---|---|---|---|
34bc5712eb | |||
70c54dc255 | |||
6da17751ca | |||
2ba5a1e963 | |||
3538dd224d | |||
b610b850ac | |||
dff919493a | |||
3164d40e22 | |||
f454512619 |
19
README.md
19
README.md
@ -14,3 +14,22 @@ that you can put anywhere in your path.
|
|||||||
* [repo Manifest Format](./docs/manifest-format.md)
|
* [repo Manifest Format](./docs/manifest-format.md)
|
||||||
* [repo Hooks](./docs/repo-hooks.md)
|
* [repo Hooks](./docs/repo-hooks.md)
|
||||||
* [Submitting patches](./SUBMITTING_PATCHES.md)
|
* [Submitting patches](./SUBMITTING_PATCHES.md)
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
Many distros include repo, so you might be able to install from there.
|
||||||
|
```sh
|
||||||
|
# Debian/Ubuntu.
|
||||||
|
$ sudo apt-get install repo
|
||||||
|
|
||||||
|
# Gentoo.
|
||||||
|
$ sudo emerge dev-vcs/repo
|
||||||
|
```
|
||||||
|
|
||||||
|
You can install it manually as well as it's a single script.
|
||||||
|
```sh
|
||||||
|
$ mkdir -p ~/.bin
|
||||||
|
$ PATH="${HOME}/.bin:${PATH}"
|
||||||
|
$ curl https://storage.googleapis.com/git-repo-downloads/repo > ~/.bin/repo
|
||||||
|
$ chmod a+rx ~/.bin/repo
|
||||||
|
```
|
||||||
|
20
editor.py
20
editor.py
@ -68,11 +68,14 @@ least one of these before using this command.""", file=sys.stderr)
|
|||||||
def EditString(cls, data):
|
def EditString(cls, data):
|
||||||
"""Opens an editor to edit the given content.
|
"""Opens an editor to edit the given content.
|
||||||
|
|
||||||
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.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
EditorError: The editor failed to run.
|
||||||
"""
|
"""
|
||||||
editor = cls._GetEditor()
|
editor = cls._GetEditor()
|
||||||
if editor == ':':
|
if editor == ':':
|
||||||
@ -80,7 +83,7 @@ least one of these before using this command.""", file=sys.stderr)
|
|||||||
|
|
||||||
fd, path = tempfile.mkstemp()
|
fd, path = tempfile.mkstemp()
|
||||||
try:
|
try:
|
||||||
os.write(fd, data)
|
os.write(fd, data.encode('utf-8'))
|
||||||
os.close(fd)
|
os.close(fd)
|
||||||
fd = None
|
fd = None
|
||||||
|
|
||||||
@ -106,11 +109,8 @@ least one of these before using this command.""", file=sys.stderr)
|
|||||||
raise EditorError('editor failed with exit status %d: %s %s'
|
raise EditorError('editor failed with exit status %d: %s %s'
|
||||||
% (rc, editor, path))
|
% (rc, editor, path))
|
||||||
|
|
||||||
fd2 = open(path)
|
with open(path, mode='rb') as fd2:
|
||||||
try:
|
return fd2.read().decode('utf-8')
|
||||||
return fd2.read()
|
|
||||||
finally:
|
|
||||||
fd2.close()
|
|
||||||
finally:
|
finally:
|
||||||
if fd:
|
if fd:
|
||||||
os.close(fd)
|
os.close(fd)
|
||||||
|
@ -276,22 +276,16 @@ class GitConfig(object):
|
|||||||
return None
|
return None
|
||||||
try:
|
try:
|
||||||
Trace(': parsing %s', self.file)
|
Trace(': parsing %s', self.file)
|
||||||
fd = open(self._json)
|
with open(self._json) as fd:
|
||||||
try:
|
|
||||||
return json.load(fd)
|
return json.load(fd)
|
||||||
finally:
|
|
||||||
fd.close()
|
|
||||||
except (IOError, ValueError):
|
except (IOError, ValueError):
|
||||||
platform_utils.remove(self._json)
|
platform_utils.remove(self._json)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _SaveJson(self, cache):
|
def _SaveJson(self, cache):
|
||||||
try:
|
try:
|
||||||
fd = open(self._json, 'w')
|
with open(self._json, 'w') as fd:
|
||||||
try:
|
|
||||||
json.dump(cache, fd, indent=2)
|
json.dump(cache, fd, indent=2)
|
||||||
finally:
|
|
||||||
fd.close()
|
|
||||||
except (IOError, TypeError):
|
except (IOError, TypeError):
|
||||||
if os.path.exists(self._json):
|
if os.path.exists(self._json):
|
||||||
platform_utils.remove(self._json)
|
platform_utils.remove(self._json)
|
||||||
@ -773,15 +767,12 @@ class Branch(object):
|
|||||||
self._Set('merge', self.merge)
|
self._Set('merge', self.merge)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
fd = open(self._config.file, 'a')
|
with open(self._config.file, 'a') as fd:
|
||||||
try:
|
|
||||||
fd.write('[branch "%s"]\n' % self.name)
|
fd.write('[branch "%s"]\n' % self.name)
|
||||||
if self.remote:
|
if self.remote:
|
||||||
fd.write('\tremote = %s\n' % self.remote.name)
|
fd.write('\tremote = %s\n' % self.remote.name)
|
||||||
if self.merge:
|
if self.merge:
|
||||||
fd.write('\tmerge = %s\n' % self.merge)
|
fd.write('\tmerge = %s\n' % self.merge)
|
||||||
finally:
|
|
||||||
fd.close()
|
|
||||||
|
|
||||||
def _Set(self, key, value):
|
def _Set(self, key, value):
|
||||||
key = 'branch.%s.%s' % (self.name, key)
|
key = 'branch.%s.%s' % (self.name, key)
|
||||||
|
13
git_refs.py
13
git_refs.py
@ -141,18 +141,11 @@ class GitRefs(object):
|
|||||||
|
|
||||||
def _ReadLoose1(self, path, name):
|
def _ReadLoose1(self, path, name):
|
||||||
try:
|
try:
|
||||||
fd = open(path)
|
with open(path) as fd:
|
||||||
except IOError:
|
|
||||||
return
|
|
||||||
|
|
||||||
try:
|
|
||||||
try:
|
|
||||||
mtime = os.path.getmtime(path)
|
mtime = os.path.getmtime(path)
|
||||||
ref_id = fd.readline()
|
ref_id = fd.readline()
|
||||||
except (IOError, OSError):
|
except (IOError, OSError):
|
||||||
return
|
return
|
||||||
finally:
|
|
||||||
fd.close()
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
ref_id = ref_id.decode()
|
ref_id = ref_id.decode()
|
||||||
|
@ -241,14 +241,15 @@ def _makelongpath(path):
|
|||||||
return path
|
return path
|
||||||
|
|
||||||
|
|
||||||
def rmtree(path):
|
def rmtree(path, ignore_errors=False):
|
||||||
"""shutil.rmtree(path) wrapper with support for long paths on Windows.
|
"""shutil.rmtree(path) wrapper with support for long paths on Windows.
|
||||||
|
|
||||||
Availability: Unix, Windows."""
|
Availability: Unix, Windows."""
|
||||||
|
onerror = None
|
||||||
if isWindows():
|
if isWindows():
|
||||||
shutil.rmtree(_makelongpath(path), onerror=handle_rmtree_error)
|
path = _makelongpath(path)
|
||||||
else:
|
onerror = handle_rmtree_error
|
||||||
shutil.rmtree(path)
|
shutil.rmtree(path, ignore_errors=ignore_errors, onerror=onerror)
|
||||||
|
|
||||||
|
|
||||||
def handle_rmtree_error(function, path, excinfo):
|
def handle_rmtree_error(function, path, excinfo):
|
||||||
|
@ -39,7 +39,7 @@ class Progress(object):
|
|||||||
self._print_newline = print_newline
|
self._print_newline = print_newline
|
||||||
self._always_print_percentage = always_print_percentage
|
self._always_print_percentage = always_print_percentage
|
||||||
|
|
||||||
def update(self, inc=1):
|
def update(self, inc=1, msg=''):
|
||||||
self._done += inc
|
self._done += inc
|
||||||
|
|
||||||
if _NOT_TTY or IsTrace():
|
if _NOT_TTY or IsTrace():
|
||||||
@ -62,12 +62,13 @@ class Progress(object):
|
|||||||
|
|
||||||
if self._lastp != p or self._always_print_percentage:
|
if self._lastp != p or self._always_print_percentage:
|
||||||
self._lastp = p
|
self._lastp = p
|
||||||
sys.stderr.write('%s\r%s: %3d%% (%d%s/%d%s)%s' % (
|
sys.stderr.write('%s\r%s: %3d%% (%d%s/%d%s)%s%s%s' % (
|
||||||
CSI_ERASE_LINE,
|
CSI_ERASE_LINE,
|
||||||
self._title,
|
self._title,
|
||||||
p,
|
p,
|
||||||
self._done, self._units,
|
self._done, self._units,
|
||||||
self._total, self._units,
|
self._total, self._units,
|
||||||
|
' ' if msg else '', msg,
|
||||||
"\n" if self._print_newline else ""))
|
"\n" if self._print_newline else ""))
|
||||||
sys.stderr.flush()
|
sys.stderr.flush()
|
||||||
|
|
||||||
|
138
project.py
138
project.py
@ -58,11 +58,8 @@ else:
|
|||||||
def _lwrite(path, content):
|
def _lwrite(path, content):
|
||||||
lock = '%s.lock' % path
|
lock = '%s.lock' % path
|
||||||
|
|
||||||
fd = open(lock, 'w')
|
with open(lock, 'w') as fd:
|
||||||
try:
|
|
||||||
fd.write(content)
|
fd.write(content)
|
||||||
finally:
|
|
||||||
fd.close()
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
platform_utils.rename(lock, path)
|
platform_utils.rename(lock, path)
|
||||||
@ -137,6 +134,7 @@ class DownloadedChange(object):
|
|||||||
|
|
||||||
class ReviewableBranch(object):
|
class ReviewableBranch(object):
|
||||||
_commit_cache = None
|
_commit_cache = None
|
||||||
|
_base_exists = None
|
||||||
|
|
||||||
def __init__(self, project, branch, base):
|
def __init__(self, project, branch, base):
|
||||||
self.project = project
|
self.project = project
|
||||||
@ -150,14 +148,19 @@ class ReviewableBranch(object):
|
|||||||
@property
|
@property
|
||||||
def commits(self):
|
def commits(self):
|
||||||
if self._commit_cache is None:
|
if self._commit_cache is None:
|
||||||
self._commit_cache = self.project.bare_git.rev_list('--abbrev=8',
|
args = ('--abbrev=8', '--abbrev-commit', '--pretty=oneline', '--reverse',
|
||||||
'--abbrev-commit',
|
'--date-order', not_rev(self.base), R_HEADS + self.name, '--')
|
||||||
'--pretty=oneline',
|
try:
|
||||||
'--reverse',
|
self._commit_cache = self.project.bare_git.rev_list(*args)
|
||||||
'--date-order',
|
except GitError:
|
||||||
not_rev(self.base),
|
# We weren't able to probe the commits for this branch. Was it tracking
|
||||||
R_HEADS + self.name,
|
# a branch that no longer exists? If so, return no commits. Otherwise,
|
||||||
'--')
|
# rethrow the error as we don't know what's going on.
|
||||||
|
if self.base_exists:
|
||||||
|
raise
|
||||||
|
|
||||||
|
self._commit_cache = []
|
||||||
|
|
||||||
return self._commit_cache
|
return self._commit_cache
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -176,6 +179,23 @@ class ReviewableBranch(object):
|
|||||||
R_HEADS + self.name,
|
R_HEADS + self.name,
|
||||||
'--')
|
'--')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def base_exists(self):
|
||||||
|
"""Whether the branch we're tracking exists.
|
||||||
|
|
||||||
|
Normally it should, but sometimes branches we track can get deleted.
|
||||||
|
"""
|
||||||
|
if self._base_exists is None:
|
||||||
|
try:
|
||||||
|
self.project.bare_git.rev_parse('--verify', not_rev(self.base))
|
||||||
|
# If we're still here, the base branch exists.
|
||||||
|
self._base_exists = True
|
||||||
|
except GitError:
|
||||||
|
# If we failed to verify, the base branch doesn't exist.
|
||||||
|
self._base_exists = False
|
||||||
|
|
||||||
|
return self._base_exists
|
||||||
|
|
||||||
def UploadForReview(self, people,
|
def UploadForReview(self, people,
|
||||||
auto_topic=False,
|
auto_topic=False,
|
||||||
draft=False,
|
draft=False,
|
||||||
@ -1393,12 +1413,9 @@ class Project(object):
|
|||||||
if is_new:
|
if is_new:
|
||||||
alt = os.path.join(self.gitdir, 'objects/info/alternates')
|
alt = os.path.join(self.gitdir, 'objects/info/alternates')
|
||||||
try:
|
try:
|
||||||
fd = open(alt)
|
with open(alt) as fd:
|
||||||
try:
|
|
||||||
# This works for both absolute and relative alternate directories.
|
# This works for both absolute and relative alternate directories.
|
||||||
alt_dir = os.path.join(self.objdir, 'objects', fd.readline().rstrip())
|
alt_dir = os.path.join(self.objdir, 'objects', fd.readline().rstrip())
|
||||||
finally:
|
|
||||||
fd.close()
|
|
||||||
except IOError:
|
except IOError:
|
||||||
alt_dir = None
|
alt_dir = None
|
||||||
else:
|
else:
|
||||||
@ -1505,6 +1522,13 @@ class Project(object):
|
|||||||
"""Perform only the local IO portion of the sync process.
|
"""Perform only the local IO portion of the sync process.
|
||||||
Network access is not required.
|
Network access is not required.
|
||||||
"""
|
"""
|
||||||
|
if not os.path.exists(self.gitdir):
|
||||||
|
syncbuf.fail(self,
|
||||||
|
'Cannot checkout %s due to missing network sync; Run '
|
||||||
|
'`repo sync -n %s` first.' %
|
||||||
|
(self.name, self.name))
|
||||||
|
return
|
||||||
|
|
||||||
self._InitWorkTree(force_sync=force_sync, submodules=submodules)
|
self._InitWorkTree(force_sync=force_sync, submodules=submodules)
|
||||||
all_refs = self.bare_ref.all
|
all_refs = self.bare_ref.all
|
||||||
self.CleanPublishedCache(all_refs)
|
self.CleanPublishedCache(all_refs)
|
||||||
@ -1585,7 +1609,16 @@ class Project(object):
|
|||||||
return
|
return
|
||||||
|
|
||||||
upstream_gain = self._revlist(not_rev(HEAD), revid)
|
upstream_gain = self._revlist(not_rev(HEAD), revid)
|
||||||
pub = self.WasPublished(branch.name, all_refs)
|
|
||||||
|
# See if we can perform a fast forward merge. This can happen if our
|
||||||
|
# branch isn't in the exact same state as we last published.
|
||||||
|
try:
|
||||||
|
self.work_git.merge_base('--is-ancestor', HEAD, revid)
|
||||||
|
# Skip the published logic.
|
||||||
|
pub = False
|
||||||
|
except GitError:
|
||||||
|
pub = self.WasPublished(branch.name, all_refs)
|
||||||
|
|
||||||
if pub:
|
if pub:
|
||||||
not_merged = self._revlist(not_rev(revid), pub)
|
not_merged = self._revlist(not_rev(revid), pub)
|
||||||
if not_merged:
|
if not_merged:
|
||||||
@ -2706,41 +2739,45 @@ class Project(object):
|
|||||||
raise
|
raise
|
||||||
|
|
||||||
def _InitWorkTree(self, force_sync=False, submodules=False):
|
def _InitWorkTree(self, force_sync=False, submodules=False):
|
||||||
dotgit = os.path.join(self.worktree, '.git')
|
realdotgit = os.path.join(self.worktree, '.git')
|
||||||
init_dotgit = not os.path.exists(dotgit)
|
tmpdotgit = realdotgit + '.tmp'
|
||||||
|
init_dotgit = not os.path.exists(realdotgit)
|
||||||
|
if init_dotgit:
|
||||||
|
dotgit = tmpdotgit
|
||||||
|
platform_utils.rmtree(tmpdotgit, ignore_errors=True)
|
||||||
|
os.makedirs(tmpdotgit)
|
||||||
|
self._ReferenceGitDir(self.gitdir, tmpdotgit, share_refs=True,
|
||||||
|
copy_all=False)
|
||||||
|
else:
|
||||||
|
dotgit = realdotgit
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if init_dotgit:
|
self._CheckDirReference(self.gitdir, dotgit, share_refs=True)
|
||||||
os.makedirs(dotgit)
|
except GitError as e:
|
||||||
self._ReferenceGitDir(self.gitdir, dotgit, share_refs=True,
|
if force_sync and not init_dotgit:
|
||||||
copy_all=False)
|
try:
|
||||||
|
platform_utils.rmtree(dotgit)
|
||||||
|
return self._InitWorkTree(force_sync=False, submodules=submodules)
|
||||||
|
except:
|
||||||
|
raise e
|
||||||
|
raise e
|
||||||
|
|
||||||
try:
|
if init_dotgit:
|
||||||
self._CheckDirReference(self.gitdir, dotgit, share_refs=True)
|
_lwrite(os.path.join(tmpdotgit, HEAD), '%s\n' % self.GetRevisionId())
|
||||||
except GitError as e:
|
|
||||||
if force_sync:
|
|
||||||
try:
|
|
||||||
platform_utils.rmtree(dotgit)
|
|
||||||
return self._InitWorkTree(force_sync=False, submodules=submodules)
|
|
||||||
except:
|
|
||||||
raise e
|
|
||||||
raise e
|
|
||||||
|
|
||||||
if init_dotgit:
|
# Now that the .git dir is fully set up, move it to its final home.
|
||||||
_lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
|
platform_utils.rename(tmpdotgit, realdotgit)
|
||||||
|
|
||||||
cmd = ['read-tree', '--reset', '-u']
|
# Finish checking out the worktree.
|
||||||
cmd.append('-v')
|
cmd = ['read-tree', '--reset', '-u']
|
||||||
cmd.append(HEAD)
|
cmd.append('-v')
|
||||||
if GitCommand(self, cmd).Wait() != 0:
|
cmd.append(HEAD)
|
||||||
raise GitError("cannot initialize work tree for " + self.name)
|
if GitCommand(self, cmd).Wait() != 0:
|
||||||
|
raise GitError('Cannot initialize work tree for ' + self.name)
|
||||||
|
|
||||||
if submodules:
|
if submodules:
|
||||||
self._SyncSubmodules(quiet=True)
|
self._SyncSubmodules(quiet=True)
|
||||||
self._CopyAndLinkFiles()
|
self._CopyAndLinkFiles()
|
||||||
except Exception:
|
|
||||||
if init_dotgit:
|
|
||||||
platform_utils.rmtree(dotgit)
|
|
||||||
raise
|
|
||||||
|
|
||||||
def _get_symlink_error_message(self):
|
def _get_symlink_error_message(self):
|
||||||
if platform_utils.isWindows():
|
if platform_utils.isWindows():
|
||||||
@ -2889,13 +2926,10 @@ class Project(object):
|
|||||||
else:
|
else:
|
||||||
path = os.path.join(self._project.worktree, '.git', HEAD)
|
path = os.path.join(self._project.worktree, '.git', HEAD)
|
||||||
try:
|
try:
|
||||||
fd = open(path)
|
with open(path) as fd:
|
||||||
|
line = fd.readline()
|
||||||
except IOError as e:
|
except IOError as e:
|
||||||
raise NoManifestException(path, str(e))
|
raise NoManifestException(path, str(e))
|
||||||
try:
|
|
||||||
line = fd.readline()
|
|
||||||
finally:
|
|
||||||
fd.close()
|
|
||||||
try:
|
try:
|
||||||
line = line.decode()
|
line = line.decode()
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
|
5
repo
5
repo
@ -513,9 +513,8 @@ def SetupGnuPG(quiet):
|
|||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
print()
|
print()
|
||||||
|
|
||||||
fd = open(os.path.join(home_dot_repo, 'keyring-version'), 'w')
|
with open(os.path.join(home_dot_repo, 'keyring-version'), 'w') as fd:
|
||||||
fd.write('.'.join(map(str, KEYRING_VERSION)) + '\n')
|
fd.write('.'.join(map(str, KEYRING_VERSION)) + '\n')
|
||||||
fd.close()
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
@ -40,10 +40,9 @@ in a Git repository for use during future 'repo init' invocations.
|
|||||||
helptext = self._helpDescription + '\n'
|
helptext = self._helpDescription + '\n'
|
||||||
r = os.path.dirname(__file__)
|
r = os.path.dirname(__file__)
|
||||||
r = os.path.dirname(r)
|
r = os.path.dirname(r)
|
||||||
fd = open(os.path.join(r, 'docs', 'manifest-format.md'))
|
with open(os.path.join(r, 'docs', 'manifest-format.md')) as fd:
|
||||||
for line in fd:
|
for line in fd:
|
||||||
helptext += line
|
helptext += line
|
||||||
fd.close()
|
|
||||||
return helptext
|
return helptext
|
||||||
|
|
||||||
def _Options(self, p):
|
def _Options(self, p):
|
||||||
|
@ -51,11 +51,16 @@ class Prune(PagedCommand):
|
|||||||
out.project('project %s/' % project.relpath)
|
out.project('project %s/' % project.relpath)
|
||||||
out.nl()
|
out.nl()
|
||||||
|
|
||||||
commits = branch.commits
|
print('%s %-33s ' % (
|
||||||
date = branch.date
|
|
||||||
print('%s %-33s (%2d commit%s, %s)' % (
|
|
||||||
branch.name == project.CurrentBranch and '*' or ' ',
|
branch.name == project.CurrentBranch and '*' or ' ',
|
||||||
branch.name,
|
branch.name), end='')
|
||||||
|
|
||||||
|
if not branch.base_exists:
|
||||||
|
print('(ignoring: tracking branch is gone: %s)' % (branch.base,))
|
||||||
|
else:
|
||||||
|
commits = branch.commits
|
||||||
|
date = branch.date
|
||||||
|
print('(%2d commit%s, %s)' % (
|
||||||
len(commits),
|
len(commits),
|
||||||
len(commits) != 1 and 's' or ' ',
|
len(commits) != 1 and 's' or ' ',
|
||||||
date))
|
date))
|
||||||
|
@ -315,9 +315,6 @@ later is required to fix a server side protocol bug.
|
|||||||
# We'll set to true once we've locked the lock.
|
# We'll set to true once we've locked the lock.
|
||||||
did_lock = False
|
did_lock = False
|
||||||
|
|
||||||
if not opt.quiet:
|
|
||||||
print('Fetching project %s' % project.name)
|
|
||||||
|
|
||||||
# Encapsulate everything in a try/except/finally so that:
|
# Encapsulate everything in a try/except/finally so that:
|
||||||
# - We always set err_event in the case of an exception.
|
# - We always set err_event in the case of an exception.
|
||||||
# - We always make sure we unlock the lock if we locked it.
|
# - We always make sure we unlock the lock if we locked it.
|
||||||
@ -350,7 +347,7 @@ later is required to fix a server side protocol bug.
|
|||||||
raise _FetchError()
|
raise _FetchError()
|
||||||
|
|
||||||
fetched.add(project.gitdir)
|
fetched.add(project.gitdir)
|
||||||
pm.update()
|
pm.update(msg=project.name)
|
||||||
except _FetchError:
|
except _FetchError:
|
||||||
pass
|
pass
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@ -371,7 +368,6 @@ later is required to fix a server side protocol bug.
|
|||||||
fetched = set()
|
fetched = set()
|
||||||
lock = _threading.Lock()
|
lock = _threading.Lock()
|
||||||
pm = Progress('Fetching projects', len(projects),
|
pm = Progress('Fetching projects', len(projects),
|
||||||
print_newline=not(opt.quiet),
|
|
||||||
always_print_percentage=opt.quiet)
|
always_print_percentage=opt.quiet)
|
||||||
|
|
||||||
objdir_project_map = dict()
|
objdir_project_map = dict()
|
||||||
@ -440,7 +436,7 @@ later is required to fix a server side protocol bug.
|
|||||||
finally:
|
finally:
|
||||||
sem.release()
|
sem.release()
|
||||||
|
|
||||||
def _CheckoutOne(self, opt, project, lock, pm, err_event):
|
def _CheckoutOne(self, opt, project, lock, pm, err_event, err_results):
|
||||||
"""Checkout work tree for one project
|
"""Checkout work tree for one project
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -452,6 +448,8 @@ later is required to fix a server side protocol bug.
|
|||||||
lock held).
|
lock held).
|
||||||
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).
|
||||||
|
err_results: A list of strings, paths to git repos where checkout
|
||||||
|
failed.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Whether the fetch was successful.
|
Whether the fetch was successful.
|
||||||
@ -459,9 +457,6 @@ later is required to fix a server side protocol bug.
|
|||||||
# We'll set to true once we've locked the lock.
|
# We'll set to true once we've locked the lock.
|
||||||
did_lock = False
|
did_lock = False
|
||||||
|
|
||||||
if not opt.quiet:
|
|
||||||
print('Checking out project %s' % project.name)
|
|
||||||
|
|
||||||
# Encapsulate everything in a try/except/finally so that:
|
# Encapsulate everything in a try/except/finally so that:
|
||||||
# - We always set err_event in the case of an exception.
|
# - We always set err_event in the case of an exception.
|
||||||
# - We always make sure we unlock the lock if we locked it.
|
# - We always make sure we unlock the lock if we locked it.
|
||||||
@ -472,11 +467,11 @@ later is required to fix a server side protocol bug.
|
|||||||
try:
|
try:
|
||||||
try:
|
try:
|
||||||
project.Sync_LocalHalf(syncbuf, force_sync=opt.force_sync)
|
project.Sync_LocalHalf(syncbuf, force_sync=opt.force_sync)
|
||||||
success = syncbuf.Finish()
|
|
||||||
|
|
||||||
# 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()
|
||||||
|
success = syncbuf.Finish()
|
||||||
did_lock = True
|
did_lock = True
|
||||||
|
|
||||||
if not success:
|
if not success:
|
||||||
@ -485,7 +480,7 @@ later is required to fix a server side protocol bug.
|
|||||||
file=sys.stderr)
|
file=sys.stderr)
|
||||||
raise _CheckoutError()
|
raise _CheckoutError()
|
||||||
|
|
||||||
pm.update()
|
pm.update(msg=project.name)
|
||||||
except _CheckoutError:
|
except _CheckoutError:
|
||||||
pass
|
pass
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@ -496,6 +491,8 @@ later is required to fix a server side protocol bug.
|
|||||||
raise
|
raise
|
||||||
finally:
|
finally:
|
||||||
if did_lock:
|
if did_lock:
|
||||||
|
if not success:
|
||||||
|
err_results.append(project.relpath)
|
||||||
lock.release()
|
lock.release()
|
||||||
finish = time.time()
|
finish = time.time()
|
||||||
self.event_log.AddSync(project, event_log.TASK_SYNC_LOCAL,
|
self.event_log.AddSync(project, event_log.TASK_SYNC_LOCAL,
|
||||||
@ -523,11 +520,12 @@ later is required to fix a server side protocol bug.
|
|||||||
syncjobs = 1
|
syncjobs = 1
|
||||||
|
|
||||||
lock = _threading.Lock()
|
lock = _threading.Lock()
|
||||||
pm = Progress('Syncing work tree', len(all_projects))
|
pm = Progress('Checking out projects', len(all_projects))
|
||||||
|
|
||||||
threads = set()
|
threads = set()
|
||||||
sem = _threading.Semaphore(syncjobs)
|
sem = _threading.Semaphore(syncjobs)
|
||||||
err_event = _threading.Event()
|
err_event = _threading.Event()
|
||||||
|
err_results = []
|
||||||
|
|
||||||
for project in all_projects:
|
for project in all_projects:
|
||||||
# Check for any errors before running any more tasks.
|
# Check for any errors before running any more tasks.
|
||||||
@ -542,7 +540,8 @@ later is required to fix a server side protocol bug.
|
|||||||
project=project,
|
project=project,
|
||||||
lock=lock,
|
lock=lock,
|
||||||
pm=pm,
|
pm=pm,
|
||||||
err_event=err_event)
|
err_event=err_event,
|
||||||
|
err_results=err_results)
|
||||||
if syncjobs > 1:
|
if syncjobs > 1:
|
||||||
t = _threading.Thread(target=self._CheckoutWorker,
|
t = _threading.Thread(target=self._CheckoutWorker,
|
||||||
kwargs=kwargs)
|
kwargs=kwargs)
|
||||||
@ -560,6 +559,9 @@ later is required to fix a server side protocol bug.
|
|||||||
# 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('\nerror: Exited sync due to checkout errors', file=sys.stderr)
|
print('\nerror: Exited sync due to checkout errors', file=sys.stderr)
|
||||||
|
if err_results:
|
||||||
|
print('Failing repos:\n%s' % '\n'.join(err_results),
|
||||||
|
file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
def _GCProjects(self, projects):
|
def _GCProjects(self, projects):
|
||||||
@ -692,11 +694,8 @@ later is required to fix a server side protocol bug.
|
|||||||
old_project_paths = []
|
old_project_paths = []
|
||||||
|
|
||||||
if os.path.exists(file_path):
|
if os.path.exists(file_path):
|
||||||
fd = open(file_path, 'r')
|
with open(file_path, 'r') as fd:
|
||||||
try:
|
|
||||||
old_project_paths = fd.read().split('\n')
|
old_project_paths = fd.read().split('\n')
|
||||||
finally:
|
|
||||||
fd.close()
|
|
||||||
# In reversed order, so subfolders are deleted before parent folder.
|
# In reversed order, so subfolders are deleted before parent folder.
|
||||||
for path in sorted(old_project_paths, reverse=True):
|
for path in sorted(old_project_paths, reverse=True):
|
||||||
if not path:
|
if not path:
|
||||||
@ -731,12 +730,9 @@ later is required to fix a server side protocol bug.
|
|||||||
return 1
|
return 1
|
||||||
|
|
||||||
new_project_paths.sort()
|
new_project_paths.sort()
|
||||||
fd = open(file_path, 'w')
|
with open(file_path, 'w') as fd:
|
||||||
try:
|
|
||||||
fd.write('\n'.join(new_project_paths))
|
fd.write('\n'.join(new_project_paths))
|
||||||
fd.write('\n')
|
fd.write('\n')
|
||||||
finally:
|
|
||||||
fd.close()
|
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
def _SmartSyncSetup(self, opt, smart_sync_manifest_path):
|
def _SmartSyncSetup(self, opt, smart_sync_manifest_path):
|
||||||
@ -809,11 +805,8 @@ later is required to fix a server side protocol bug.
|
|||||||
if success:
|
if success:
|
||||||
manifest_name = os.path.basename(smart_sync_manifest_path)
|
manifest_name = os.path.basename(smart_sync_manifest_path)
|
||||||
try:
|
try:
|
||||||
f = open(smart_sync_manifest_path, 'w')
|
with open(smart_sync_manifest_path, 'w') as f:
|
||||||
try:
|
|
||||||
f.write(manifest_str)
|
f.write(manifest_str)
|
||||||
finally:
|
|
||||||
f.close()
|
|
||||||
except IOError as e:
|
except IOError as e:
|
||||||
print('error: cannot write manifest to %s:\n%s'
|
print('error: cannot write manifest to %s:\n%s'
|
||||||
% (smart_sync_manifest_path, e),
|
% (smart_sync_manifest_path, e),
|
||||||
@ -1102,11 +1095,8 @@ 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)
|
with open(self._path) as f:
|
||||||
try:
|
|
||||||
self._times = json.load(f)
|
self._times = json.load(f)
|
||||||
finally:
|
|
||||||
f.close()
|
|
||||||
except (IOError, ValueError):
|
except (IOError, ValueError):
|
||||||
try:
|
try:
|
||||||
platform_utils.remove(self._path)
|
platform_utils.remove(self._path)
|
||||||
@ -1126,11 +1116,8 @@ class _FetchTimes(object):
|
|||||||
del self._times[name]
|
del self._times[name]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
f = open(self._path, 'w')
|
with open(self._path, 'w') as f:
|
||||||
try:
|
|
||||||
json.dump(self._times, f, indent=2)
|
json.dump(self._times, f, indent=2)
|
||||||
finally:
|
|
||||||
f.close()
|
|
||||||
except (IOError, TypeError):
|
except (IOError, TypeError):
|
||||||
try:
|
try:
|
||||||
platform_utils.remove(self._path)
|
platform_utils.remove(self._path)
|
||||||
|
@ -271,11 +271,6 @@ Gerrit Code Review: https://www.gerritcodereview.com/
|
|||||||
branches[project.name] = b
|
branches[project.name] = b
|
||||||
script.append('')
|
script.append('')
|
||||||
|
|
||||||
script = [ x.encode('utf-8')
|
|
||||||
if issubclass(type(x), unicode)
|
|
||||||
else x
|
|
||||||
for x in script ]
|
|
||||||
|
|
||||||
script = Editor.EditString("\n".join(script)).split("\n")
|
script = Editor.EditString("\n".join(script)).split("\n")
|
||||||
|
|
||||||
project_re = re.compile(r'^#?\s*project\s*([^\s]+)/:$')
|
project_re = re.compile(r'^#?\s*project\s*([^\s]+)/:$')
|
||||||
|
60
tests/test_editor.py
Normal file
60
tests/test_editor.py
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
|
#
|
||||||
|
# Copyright (C) 2019 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.
|
||||||
|
|
||||||
|
"""Unittests for the editor.py module."""
|
||||||
|
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
from editor import Editor
|
||||||
|
|
||||||
|
|
||||||
|
class EditorTestCase(unittest.TestCase):
|
||||||
|
"""Take care of resetting Editor state across tests."""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.setEditor(None)
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
self.setEditor(None)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def setEditor(editor):
|
||||||
|
Editor._editor = editor
|
||||||
|
|
||||||
|
|
||||||
|
class GetEditor(EditorTestCase):
|
||||||
|
"""Check GetEditor behavior."""
|
||||||
|
|
||||||
|
def test_basic(self):
|
||||||
|
"""Basic checking of _GetEditor."""
|
||||||
|
self.setEditor(':')
|
||||||
|
self.assertEqual(':', Editor._GetEditor())
|
||||||
|
|
||||||
|
|
||||||
|
class EditString(EditorTestCase):
|
||||||
|
"""Check EditString behavior."""
|
||||||
|
|
||||||
|
def test_no_editor(self):
|
||||||
|
"""Check behavior when no editor is available."""
|
||||||
|
self.setEditor(':')
|
||||||
|
self.assertEqual('foo', Editor.EditString('foo'))
|
||||||
|
|
||||||
|
def test_cat_editor(self):
|
||||||
|
"""Check behavior when editor is `cat`."""
|
||||||
|
self.setEditor('cat')
|
||||||
|
self.assertEqual('foo', Editor.EditString('foo'))
|
@ -18,11 +18,30 @@
|
|||||||
|
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
|
|
||||||
|
import contextlib
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
import tempfile
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
|
import git_config
|
||||||
import project
|
import project
|
||||||
|
|
||||||
|
|
||||||
|
@contextlib.contextmanager
|
||||||
|
def TempGitTree():
|
||||||
|
"""Create a new empty git checkout for testing."""
|
||||||
|
# TODO(vapier): Convert this to tempfile.TemporaryDirectory once we drop
|
||||||
|
# Python 2 support entirely.
|
||||||
|
try:
|
||||||
|
tempdir = tempfile.mkdtemp(prefix='repo-tests')
|
||||||
|
subprocess.check_call(['git', 'init'], cwd=tempdir)
|
||||||
|
yield tempdir
|
||||||
|
finally:
|
||||||
|
shutil.rmtree(tempdir)
|
||||||
|
|
||||||
|
|
||||||
class RepoHookShebang(unittest.TestCase):
|
class RepoHookShebang(unittest.TestCase):
|
||||||
"""Check shebang parsing in RepoHook."""
|
"""Check shebang parsing in RepoHook."""
|
||||||
|
|
||||||
@ -60,3 +79,58 @@ class RepoHookShebang(unittest.TestCase):
|
|||||||
for shebang, interp in DATA:
|
for shebang, interp in DATA:
|
||||||
self.assertEqual(project.RepoHook._ExtractInterpFromShebang(shebang),
|
self.assertEqual(project.RepoHook._ExtractInterpFromShebang(shebang),
|
||||||
interp)
|
interp)
|
||||||
|
|
||||||
|
|
||||||
|
class FakeProject(object):
|
||||||
|
"""A fake for Project for basic functionality."""
|
||||||
|
|
||||||
|
def __init__(self, worktree):
|
||||||
|
self.worktree = worktree
|
||||||
|
self.gitdir = os.path.join(worktree, '.git')
|
||||||
|
self.name = 'fakeproject'
|
||||||
|
self.work_git = project.Project._GitGetByExec(
|
||||||
|
self, bare=False, gitdir=self.gitdir)
|
||||||
|
self.bare_git = project.Project._GitGetByExec(
|
||||||
|
self, bare=True, gitdir=self.gitdir)
|
||||||
|
self.config = git_config.GitConfig.ForRepository(gitdir=self.gitdir)
|
||||||
|
|
||||||
|
|
||||||
|
class ReviewableBranchTests(unittest.TestCase):
|
||||||
|
"""Check ReviewableBranch behavior."""
|
||||||
|
|
||||||
|
def test_smoke(self):
|
||||||
|
"""A quick run through everything."""
|
||||||
|
with TempGitTree() as tempdir:
|
||||||
|
fakeproj = FakeProject(tempdir)
|
||||||
|
|
||||||
|
# Generate some commits.
|
||||||
|
with open(os.path.join(tempdir, 'readme'), 'w') as fp:
|
||||||
|
fp.write('txt')
|
||||||
|
fakeproj.work_git.add('readme')
|
||||||
|
fakeproj.work_git.commit('-mAdd file')
|
||||||
|
fakeproj.work_git.checkout('-b', 'work')
|
||||||
|
fakeproj.work_git.rm('-f', 'readme')
|
||||||
|
fakeproj.work_git.commit('-mDel file')
|
||||||
|
|
||||||
|
# Start off with the normal details.
|
||||||
|
rb = project.ReviewableBranch(
|
||||||
|
fakeproj, fakeproj.config.GetBranch('work'), 'master')
|
||||||
|
self.assertEqual('work', rb.name)
|
||||||
|
self.assertEqual(1, len(rb.commits))
|
||||||
|
self.assertIn('Del file', rb.commits[0])
|
||||||
|
d = rb.unabbrev_commits
|
||||||
|
self.assertEqual(1, len(d))
|
||||||
|
short, long = next(iter(d.items()))
|
||||||
|
self.assertTrue(long.startswith(short))
|
||||||
|
self.assertTrue(rb.base_exists)
|
||||||
|
# Hard to assert anything useful about this.
|
||||||
|
self.assertTrue(rb.date)
|
||||||
|
|
||||||
|
# Now delete the tracking branch!
|
||||||
|
fakeproj.work_git.branch('-D', 'master')
|
||||||
|
rb = project.ReviewableBranch(
|
||||||
|
fakeproj, fakeproj.config.GetBranch('work'), 'master')
|
||||||
|
self.assertEqual(0, len(rb.commits))
|
||||||
|
self.assertFalse(rb.base_exists)
|
||||||
|
# Hard to assert anything useful about this.
|
||||||
|
self.assertTrue(rb.date)
|
||||||
|
Reference in New Issue
Block a user