def get_version(self, spec=None): """ :param spec: (optional) revisionspec of desired version. May be any revisionspec as returned by 'bzr help revisionspec', e.g. a tagname or 'revno:<number>' :returns: the current revision number of the repository. Or if spec is provided, the number of a revision specified by some token. """ if self.detect_presence(): if spec is not None: command = ['bzr log -r %s .' % sanitized(spec)] _, output, _ = run_shell_command(command, shell=True, cwd=self._path, us_env=True) if output is None or output.strip() == '' or output.startswith("bzr:"): return None else: matches = [l for l in output.split('\n') if l.startswith('revno: ')] if len(matches) == 1: return matches[0].split()[1] else: _, output, _ = run_shell_command('bzr revno --tree', shell=True, cwd=self._path, us_env=True) return output.strip()
def checkout(self, url, version='', verbose=False, shallow=False): if url is None or url.strip() == '': raise ValueError('Invalid empty url : "%s"' % url) # make sure that the parent directory exists for #3497 base_path = os.path.split(self.get_path())[0] try: os.makedirs(base_path) except OSError: # OSError thrown if directory already exists this is ok pass cmd = "hg clone %s %s" % (sanitized(url), self._path) value, _, msg = run_shell_command(cmd, shell=True, no_filter=True) if value != 0: if msg: sys.logger.error('%s' % msg) return False if version is not None and version.strip() != '': cmd = "hg checkout %s" % sanitized(version) value, _, msg = run_shell_command(cmd, cwd=self._path, shell=True, no_filter=True) if value != 0: if msg: sys.stderr.write('%s\n' % msg) return False return True
def get_status(self, basepath=None, untracked=False): response = None if basepath is None: basepath = self._path if self.path_exists(): rel_path = normalized_rel_path(self._path, basepath) # git command only works inside repo # self._path is safe against command injection, as long as we check path.exists command = "git status -s " if not untracked: command += " -uno" _, response, _ = run_shell_command(command, shell=True, cwd=self._path) response_processed = "" for line in response.split("\n"): if len(line.strip()) > 0: # prepend relative path response_processed += "%s%s/%s\n" % (line[0:3], rel_path, line[3:]) if LooseVersion(self.gitversion) > LooseVersion("1.7"): command = "git submodule foreach --recursive git status -s" if not untracked: command += " -uno" _, response2, _ = run_shell_command(command, shell=True, cwd=self._path) for line in response2.split("\n"): if line.startswith("Entering"): continue if len(line.strip()) > 0: # prepend relative path response_processed += line[0:3] + rel_path + "/" + line[3:] + "\n" response = response_processed return response
def checkout(self, url, version='', verbose=False, shallow=False): if self.path_exists(): sys.stderr.write("Error: cannot checkout into existing directory\n") return False # make sure that the parent directory exists for #3497 base_path = os.path.split(self.get_path())[0] try: os.makedirs(base_path) except OSError: # OSError thrown if directory already exists this is ok pass cmd = "hg clone %s %s" % (sanitized(url), self._path) value, _, _ = run_shell_command(cmd, shell=True, no_filter=True) if value != 0: if self.path_exists(): sys.stderr.write("Error: cannot checkout into existing directory\n") return False if version is not None and version.strip() != '': cmd = "hg checkout %s" % sanitized(version) value, _, _ = run_shell_command(cmd, cwd=self._path, shell=True, no_filter=True) if value != 0: return False return True
def _get_branch_parent(self, fetch=False, current_branch=None): """ :param fetch: if true, performs git fetch first :param current_branch: if not None, this is used as current branch (else extra shell call) :returns: (branch, remote) the name of the branch this branch tracks and its remote :raises: GitError if fetch fails """ if not self.path_exists(): return (None, None) # get name of configured merge ref. branchname = current_branch or self._get_branch() if branchname is None: return (None, None) cmd = 'git config --get %s' % sanitized('branch.%s.merge' % branchname) _, output, _ = run_shell_command(cmd, shell=True, cwd=self._path) if not output: return (None, None) lines = output.splitlines() if len(lines) > 1: sys.stderr.write("vcstools unable to handle multiple merge references for branch %s:\n%s\n" % (branchname, output)) return (None, None) # get name of configured remote cmd = 'git config --get "branch.%s.remote"' % branchname _, output2, _ = run_shell_command(cmd, shell=True, cwd=self._path) remote = output2 or self._get_default_remote() branch_reference = lines[0] # branch_reference is either refname, or /refs/heads/refname, or # heads/refname we would like to return refname however, # user could also have named any branch # "/refs/heads/refname", for some unholy reason check all # known branches on remote for refname, then for the odd # cases, as git seems to do candidate = branch_reference if candidate.startswith('refs/'): candidate = candidate[len('refs/'):] if candidate.startswith('heads/'): candidate = candidate[len('heads/'):] elif candidate.startswith('tags/'): candidate = candidate[len('tags/'):] elif candidate.startswith('remotes/'): candidate = candidate[len('remotes/'):] result = None if self._is_remote_branch(candidate, remote_name=remote, fetch=fetch): result = candidate elif branch_reference != candidate and self._is_remote_branch(branch_reference, remote_name=remote, fetch=False): result = branch_reference if result is not None: return (result, remote) return None, None
def is_commit_in_orphaned_subtree(self, version, mask_self=False, fetch=True): """ checks git log --all (the list of all commits reached by references, meaning branches or tags) for version. If it shows up, that means git garbage collection will not remove the commit. Else it would eventually be deleted. :param version: SHA IDs (if partial, caller is responsible for mismatch) :param mask_self: whether to consider direct references to this commit (rather than only references on descendants) as well :param fetch: whether fetch should be done first for remote refs :returns: True if version is not recursively referenced by a branch or tag """ if fetch: self._do_fetch() if version is not None and version != "": cmd = "git show-ref -s" _, output, _ = run_shell_command(cmd, shell=True, cwd=self._path) refs = output.splitlines() # git log over all refs except HEAD cmd = "git log " + " ".join(refs) if mask_self: # %P: parent hashes cmd += " --pretty=format:%P" else: # %H: commit hash cmd += " --pretty=format:%H" _, output, _ = run_shell_command(cmd, shell=True, cwd=self._path) for line in output.splitlines(): if line.strip("'").startswith(version): return False return True return False
def checkout(self, url, version='', verbose=False, shallow=False, timeout=None): if url is None or url.strip() == '': raise ValueError('Invalid empty url : "%s"' % url) # make sure that the parent directory exists for #3497 base_path = os.path.split(self.get_path())[0] try: os.makedirs(base_path) except OSError: # OSError thrown if directory already exists this is ok pass cmd = "hg clone %s %s" % (sanitized(url), self._path) value, _, msg = run_shell_command(cmd, shell=True, no_filter=True) if value != 0: if msg: sys.logger.error('%s' % msg) return False if version is not None and version.strip() != '': cmd = "hg checkout %s" % sanitized(version) value, _, msg = run_shell_command(cmd, cwd=self._path, shell=True, no_filter=True) if value != 0: if msg: sys.stderr.write('%s\n' % msg) return False return True
def get_version(self, spec=None): """ :param spec: (optional) revisionspec of desired version. May be any revisionspec as returned by 'bzr help revisionspec', e.g. a tagname or 'revno:<number>' :returns: the current revision number of the repository. Or if spec is provided, the number of a revision specified by some token. """ if self.detect_presence(): if spec is not None: _, output, _ = run_shell_command('bzr log -r %s .' % sanitized(spec), shell=True, cwd=self._path, us_env=True) if output is None or output.strip() == '' or output.startswith( "bzr:"): return None else: matches = [ l for l in output.split('\n') if l.startswith('revno: ') ] if len(matches) == 1: return matches[0].split()[1] else: _, output, _ = run_shell_command('bzr revno --tree', shell=True, cwd=self._path, us_env=True) return output.strip()
def test_shell_command_unix(self): self.assertEqual((0, "", None), run_shell_command("true")) self.assertEqual((1, "", None), run_shell_command("false")) # not a great test on a system where this is default _, env_langs, _ = run_shell_command("/usr/bin/env |grep LANG=", shell=True, us_env=True) self.assertTrue("LANG=en_US.UTF-8" in env_langs.splitlines())
def _do_fast_forward(self, branch_parent, fetch=True, verbose=False): """Execute git fetch if necessary, and if we can fast-foward, do so to the last fetched version using git rebase. :param branch_parent: name of branch we track :param fetch: whether fetch should be done first for remote refs :returns: True if up-to-date or after succesful fast-forward :raises: GitError when git fetch fails """ assert branch_parent is not None current_version = self.get_version() default_remote = self._get_default_remote() parent_version = self.get_version("remotes/%s/%s" % (default_remote, branch_parent)) if current_version == parent_version: return True # check if we are true ancestor of tracked branch if not self._rev_list_contains(parent_version, current_version, fetch=fetch): # if not rev_list_contains this version, we are on same # commit (checked before), have advanced, or have diverged. # Now check whether tracked branch is a true ancestor of us if self._rev_list_contains(current_version, parent_version, fetch=False): return True print("Cannot fast-forward, local repository and remote '%s' have diverged." % branch_parent) return False if verbose: print("Rebasing repository") # Rebase, do not pull, because somebody could have # commited in the meantime. if LooseVersion(self.gitversion) >= LooseVersion('1.7.1'): # --keep allows o rebase even with local changes, as long as # local changes are not in files that change between versions cmd = "git reset --keep remotes/%s/%s" % (default_remote, branch_parent) value, _, _ = run_shell_command(cmd, shell=True, cwd=self._path, show_stdout=True, verbose=verbose) if value == 0: return True else: verboseflag = '' if verbose: verboseflag = '-v' # prior to version 1.7.1, git does not know --keep # Do not merge, rebase does nothing when there are local changes cmd = "git rebase %s remotes/%s/%s" % (verboseflag, default_remote, branch_parent) value, _, _ = run_shell_command(cmd, shell=True, cwd=self._path, show_stdout=True, verbose=verbose) if value == 0: return True return False
def _get_branch_parent(self, fetch=False, current_branch=None): """ :param fetch: if true, performs git fetch first :param current_branch: if not None, this is used as current branch (else extra shell call) :returns: (branch, remote) the name of the branch this branch tracks and its remote :raises: GitError if fetch fails """ if not self.path_exists(): return (None, None) # get name of configured merge ref. branchname = current_branch or self._get_branch() if branchname is None: return (None, None) cmd = 'git config --get %s' % sanitized('branch.%s.merge' % branchname) _, output, _ = run_shell_command(cmd, shell=True, cwd=self._path) if not output: return (None, None) lines = output.splitlines() if len(lines) > 1: sys.stderr.write( "vcstools unable to handle multiple merge references for branch %s:\n%s\n" % (branchname, output)) return (None, None) # get name of configured remote cmd = 'git config --get "branch.%s.remote"' % branchname _, output2, _ = run_shell_command(cmd, shell=True, cwd=self._path) remote = output2 or self._get_default_remote() branch_reference = lines[0] # branch_reference is either refname, or /refs/heads/refname, or # heads/refname we would like to return refname however, # user could also have named any branch # "/refs/heads/refname", for some unholy reason check all # known branches on remote for refname, then for the odd # cases, as git seems to do candidate = branch_reference if candidate.startswith('refs/'): candidate = candidate[len('refs/'):] if candidate.startswith('heads/'): candidate = candidate[len('heads/'):] elif candidate.startswith('tags/'): candidate = candidate[len('tags/'):] elif candidate.startswith('remotes/'): candidate = candidate[len('remotes/'):] result = None if self._is_remote_branch(candidate, remote_name=remote, fetch=fetch): result = candidate elif branch_reference != candidate and self._is_remote_branch( branch_reference, remote_name=remote, fetch=False): result = branch_reference if result is not None: return (result, remote) return None, None
def build_debian_package(package_fetcher, package_name, apt_cache, rd_obj, levels=0, get_dependencies=False): unstable_target_distros = { 'groovy': 'quantal', 'hydro': 'raring' } target_ubuntu_distro = unstable_target_distros[rd_obj._rosdistro] level_prefix = '--' * levels print("%s> Building package %s" % (level_prefix, package_name)) deb_package_name = rd_obj.debianize_package_name(package_name) deb_package_version = rd_obj.get_version(package_name, full_version=True) + target_ubuntu_distro print("%s--> Checking if installed (%s, %s).." % (level_prefix, deb_package_name, deb_package_version)), if deb_package_name in apt_cache: installed = apt_cache[deb_package_name].installed if installed is not None and installed.version == deb_package_version: print("OK") print("%s is installed already - remove the package if you want to re-install." % (package_name)) return True print("missing!") if get_dependencies: dependencies = package_build_order([package_name], distro_name=rd_obj._rosdistro) print("%s--> Checking Dependencies:" % (level_prefix)) for dep_pkg_name in dependencies: if dep_pkg_name != package_name: print("%s---- %s....." % (level_prefix, dep_pkg_name)), debian_pkg_name = rd_obj.debianize_package_name(dep_pkg_name) if debian_pkg_name in apt_cache and apt_cache[debian_pkg_name].installed is not None: print(" OK! (installed version %s)" % apt_cache[debian_pkg_name].installed.version) else: print(" Needs build, building...") build_debian_package(package_fetcher, dep_pkg_name, apt_cache, rd_obj, levels + 1) print("%s<<-- Dependencies OKAY." % (level_prefix)) print("%s>>> Build debian package %s from repo %s" % (level_prefix, deb_package_name, package_fetcher.url(package_name))) repo_path = package_fetcher.checkout_package(package_name) client = GitClient(repo_path) deb_package_tag = deb_package_name + '_' + rd_obj.get_version(package_name, full_version=True) + '_' + target_ubuntu_distro bloom_package_version = 'debian/' + deb_package_tag client.update(bloom_package_version) installed_builddeps = install_debian_build_dependencies(repo_path) if not installed_builddeps: raise RosGitBuildError("%s!!! Error building %s from %s: Can't install build-dependencies!" % (level_prefix, deb_package_name, package_fetcher.url(package_name))) (returncode, result, message) = run_shell_command('debuild clean', repo_path, shell=True, show_stdout=False) if returncode != 0: raise RosGitBuildError("%s!!! Error building %s from %s: %s \n %s" % (level_prefix, deb_package_name, package_fetcher.url(package_name), 'debuild clean', message)) (returncode, result, message) = run_shell_command('debuild binary', repo_path, shell=True, show_stdout=False) if returncode != 0: raise RosGitBuildError("%s!!! Error building %s from %s: %s \n %s" % (level_prefix, deb_package_name, package_fetcher.url(package_name), 'debuild binary', message)) deb_files = glob.glob(os.path.join(repo_path, '..', '%s*.deb' % (deb_package_name + '_' + rd_obj.get_version(package_name, full_version=True)))) if len(deb_files) > 0: # install the deb from apt.debfile import DebPackage deb_pkg = DebPackage(deb_files[0]) deb_pkg.check() packages_needed = ' '.join(deb_pkg.missing_deps) (returncode, result, message) = run_shell_command('sudo apt-get -y install %s' % packages_needed, shell=True, show_stdout=True) if returncode != 0: raise RosGitBuildError("%s!!! Error building %s: can't install dependent packages %s" % (level_prefix, deb_package_name, packages_needed)) (returncode, result, message) = run_shell_command('sudo dpkg -i %s' % deb_files[0], shell=True, show_stdout=True) if returncode != 0: raise RosGitBuildError("%s!!! Error building %s from %s: %s \n %s" % (level_prefix, deb_package_name, package_fetcher.url(package_name), 'debuild binary', message)) else: raise RosGitBuildError("%s!!! Can't find a built debian package for %s after the build!" % (level_prefix, deb_package_name))
def _do_fast_forward(self, fetch=True, branch_parent=None, verbose=False): """Execute git fetch if necessary, and if we can fast-foward, do so to the last fetched version using git rebase. :param branch_parent: name of branch we track :param fetch: whether fetch should be done first for remote refs :returns: True if up-to-date or after succesful fast-forward :raises: GitError when git fetch fails """ assert branch_parent is not None current_version = self.get_version() parent_version = self.get_version("remotes/origin/%s" % branch_parent) if current_version == parent_version: return True # check if we are true ancestor of tracked branch if not self.rev_list_contains( parent_version, current_version, fetch=fetch): # if not rev_list_contains this version, we are on same # commit (checked before), have advanced, or have diverged. # Now check whether tracked branch is a true ancestor of us if self.rev_list_contains(current_version, parent_version, fetch=False): return True return False if verbose: print("Rebasing repository") # Rebase, do not pull, because somebody could have # commited in the meantime. if LooseVersion(self.gitversion) >= LooseVersion('1.7.1'): # --keep allows o rebase even with local changes, as long as # local changes are not in files that change between versions cmd = "git reset --keep remotes/origin/%s" % branch_parent value, _, _ = run_shell_command(cmd, shell=True, cwd=self._path, show_stdout=True, verbose=verbose) if value == 0: return True else: verboseflag = '' if verbose: verboseflag = '-v' # prior to version 1.7.1, git does not know --keep # Do not merge, rebase does nothing when there are local changes cmd = "git rebase %s remotes/origin/%s" % (verboseflag, branch_parent) value, _, _ = run_shell_command(cmd, shell=True, cwd=self._path, show_stdout=True, verbose=verbose) if value == 0: return True return False
def install_debian_build_dependencies(package_dir): (returncode, result, message) = run_shell_command('dpkg-checkbuilddeps', package_dir, shell=True, show_stdout=True) if returncode != 0: missing_deps = message.split(':')[-1].split(' ') # things with parens are versions, ignore them missing_deps = [x.strip() for x in missing_deps if x != '' and (not (x.startswith('(') or x.endswith(')')))] print ("Warning: Attempting to install missing build-deps: %s" % missing_deps) (returncode, result, message) = run_shell_command('sudo apt-get -y install %s' % ' '.join(missing_deps), package_dir, shell=True, show_stdout=True) return returncode == 0 else: return True
def get_branch_parent(self, fetch=False, current_branch=None): """ return the name of the branch this branch tracks, if any :raises: GitError if fetch fails """ if self.path_exists(): # get name of configured merge ref. branchname = current_branch or self.get_branch() if branchname is None: return None cmd = 'git config --get %s' % sanitized( 'branch.%s.merge' % branchname) _, output, _ = run_shell_command(cmd, shell=True, cwd=self._path) if not output: return None lines = output.splitlines() if len(lines) > 1: print( "vcstools unable to handle multiple merge references for branch %s:\n%s" % (branchname, output)) return None # get name of configured remote cmd = 'git config --get "branch.%s.remote"' % branchname _, output2, _ = run_shell_command(cmd, shell=True, cwd=self._path) if output2 != "origin": print( "vcstools only handles branches tracking remote 'origin', branch '%s' tracks remote '%s'" % (branchname, output2)) return None output = lines[0] # output is either refname, or /refs/heads/refname, or # heads/refname we would like to return refname however, # user could also have named any branch # "/refs/heads/refname", for some unholy reason check all # known branches on remote for refname, then for the odd # cases, as git seems to do candidate = output if candidate.startswith('refs/'): candidate = candidate[len('refs/'):] if candidate.startswith('heads/'): candidate = candidate[len('heads/'):] elif candidate.startswith('tags/'): candidate = candidate[len('tags/'):] elif candidate.startswith('remotes/'): candidate = candidate[len('remotes/'):] if self.is_remote_branch(candidate, fetch=fetch): return candidate if output != candidate and self.is_remote_branch(output, fetch=False): return output return None
def get_branch_parent(self, fetch=False, current_branch=None): """ return the name of the branch this branch tracks, if any :raises: GitError if fetch fails """ if self.path_exists(): # get name of configured merge ref. branchname = current_branch or self.get_branch() if branchname is None: return None cmd = 'git config --get %s' % sanitized('branch.%s.merge' % branchname) _, output, _ = run_shell_command(cmd, shell=True, cwd=self._path) if not output: return None lines = output.splitlines() if len(lines) > 1: print("vcstools unable to handle multiple merge references for branch %s:\n%s" % (branchname, output)) return None # get name of configured remote cmd = 'git config --get "branch.%s.remote"' % branchname _, output2, _ = run_shell_command(cmd, shell=True, cwd=self._path) if output2 != "origin": print("vcstools only handles branches tracking remote 'origin'," + " branch '%s' tracks remote '%s'" % (branchname, output2)) return None output = lines[0] # output is either refname, or /refs/heads/refname, or # heads/refname we would like to return refname however, # user could also have named any branch # "/refs/heads/refname", for some unholy reason check all # known branches on remote for refname, then for the odd # cases, as git seems to do candidate = output if candidate.startswith('refs/'): candidate = candidate[len('refs/'):] if candidate.startswith('heads/'): candidate = candidate[len('heads/'):] elif candidate.startswith('tags/'): candidate = candidate[len('tags/'):] elif candidate.startswith('remotes/'): candidate = candidate[len('remotes/'):] if self.is_remote_branch(candidate, fetch=fetch): return candidate if output != candidate and self.is_remote_branch(output, fetch=False): return output return None
def _is_commit_in_orphaned_subtree(self, version, mask_self=False, fetch=True): """ checks git log --all (the list of all commits reached by references, meaning branches or tags) for version. If it shows up, that means git garbage collection will not remove the commit. Else it would eventually be deleted. :param version: SHA IDs (if partial, caller is responsible for mismatch) :param mask_self: whether to consider direct references to this commit (rather than only references on descendants) as well :param fetch: whether fetch should be done first for remote refs :returns: True if version is not recursively referenced by a branch or tag :raises: GitError if git fetch fails """ if fetch: self._do_fetch() if version is not None and version != '': cmd = 'git show-ref -s' _, output, _ = run_shell_command(cmd, shell=True, cwd=self._path) refs = output.splitlines() if os.name == 'nt': # 20 seems like a number the Windows shell can cope with chunksize = 20 else: # 2000 seems like a number the linux shell can cope with chunksize = 2000 refchunks = [ refs[x:x + chunksize] for x in range(0, len(refs), chunksize) ] for refchunk in refchunks: # git log over all refs except HEAD cmd = 'git log ' + " ".join(refchunk) if mask_self: # %P: parent hashes cmd += " --pretty=format:%P" else: # %H: commit hash cmd += " --pretty=format:%H" _, output, _ = run_shell_command(cmd, shell=True, cwd=self._path) for line in output.splitlines(): if line.strip("'").startswith(version): return False return True return False
def update(self, version="", verbose=False, timeout=None): if not self.detect_presence(): return False value, _, _ = run_shell_command("bzr pull", cwd=self._path, shell=True, show_stdout=True, verbose=verbose) if value != 0: return False # Ignore verbose param, bzr is pretty verbose on update anyway if version is not None and version != "": cmd = "bzr update -r %s" % (version) else: cmd = "bzr update" value, _, _ = run_shell_command(cmd, cwd=self._path, shell=True, show_stdout=True, verbose=verbose) if value == 0: return True return False
def test_shell_command(self): self.assertEqual((0, "foo", None), run_shell_command("echo foo", shell=True)) (v, r, e) = run_shell_command("[", shell=True) self.assertFalse(v == 0) self.assertFalse(e is None) self.assertEqual(r, '') (v, r, e) = run_shell_command("echo foo && [", shell=True) self.assertFalse(v == 0) self.assertFalse(e is None) self.assertEqual(r, 'foo') try: run_shell_command("two words") self.fail("expected exception") except: pass
def checkout(self, url, refname=None, verbose=False, shallow=False): """calls git clone and then, if refname was given, update(refname)""" #since we cannot know whether refname names a branch, clone master initially cmd = 'git clone' if shallow: cmd += ' --depth 1' cmd += ' --recursive %s %s' % (url, self._path) value, _, _ = run_shell_command(cmd, shell=True, show_stdout=verbose, verbose=verbose) if value != 0: if self.path_exists(): sys.stderr.write("Error: cannot checkout into existing directory\n") return False try: # update to make sure we are on the right branch. Do not # check for "master" here, as default branch could be anything if refname is not None: return self.update(refname, verbose=verbose) else: return True except GitError: return False
def _do_checkout(self, refname, fetch=True, verbose=False): """ meaning git checkout, not vcstools checkout. This works for local branches, remote branches, tagnames, hashes, etc. git will create local branch of same name when no such local branch exists, and also setup tracking. Git decides with own rules whether local changes would cause conflicts, and refuses to checkout else. :raises GitError: when checkout fails """ # since refname may relate to remote branch / tag we do not # know about yet, do fetch if not already done if fetch: self._do_fetch() cmd = "git checkout %s" % (refname) value, _, _ = run_shell_command(cmd, shell=True, cwd=self._path, show_stdout=verbose, verbose=verbose) if self.cache: self.version = None if value != 0: raise GitError('Git Checkout failed')
def checkout(self, url, version='', verbose=False, shallow=False, timeout=None): if url is None or url.strip() == '': raise ValueError('Invalid empty url : "%s"' % url) # Need to check as SVN 1.6.17 writes into directory even if not empty if not ensure_dir_notexists(self.get_path()): self.logger.error("Can't remove %s" % self.get_path()) return False if version is not None and version != '': if not version.startswith("-r"): version = "-r%s" % version elif version is None: version = '' cmd = 'svn co %s %s %s' % (sanitized(version), sanitized(url), self._path) value, _, msg = run_shell_command(cmd, shell=True, no_filter=True) if value != 0: if msg: self.logger.error('%s' % msg) return False return True
def get_log(self, relpath=None, limit=None): response = [] if relpath is None: relpath = "" if self.path_exists() and os.path.exists(os.path.join(self._path, relpath)): # Get the log limit_cmd = ("--limit %d" % (int(limit))) if limit else "" HG_COMMIT_FIELDS = ["id", "author", "email", "date", "message"] HG_LOG_FORMAT = ( "\x1f".join(["{node|short}", "{author|person}", "{autor|email}", "{date|isodate}", "{desc}"]) + "\x1e" ) command = "hg log %s --template '%s' %s" % (sanitized(relpath), HG_LOG_FORMAT, limit_cmd) return_code, response_str, stderr = run_shell_command(command, shell=True, cwd=self._path) if return_code == 0: # Parse response response = response_str.strip("\n\x1e").split("\x1e") response = [row.strip().split("\x1f") for row in response] response = [dict(zip(HG_COMMIT_FIELDS, row)) for row in response] # Parse dates for entry in response: entry["date"] = dateutil.parser.parse(entry["date"]) return response
def checkout(self, url, refname=None, verbose=False, shallow=False): """calls git clone and then, if refname was given, update(refname)""" if url is None or url.strip() == '': raise ValueError('Invalid empty url : "%s"' % url) #since we cannot know whether refname names a branch, clone master initially cmd = 'git clone' if shallow: cmd += ' --depth 1' if LooseVersion(self.gitversion) >= LooseVersion('1.7.10'): cmd += ' --no-single-branch' cmd += ' --recursive %s %s' % (url, self._path) value, _, msg = run_shell_command(cmd, shell=True, show_stdout=verbose, verbose=verbose) if value != 0: if msg: self.logger.error('%s' % msg) return False try: # update to make sure we are on the right branch. Do not # check for "master" here, as default branch could be anything if refname is not None: return self._do_update(refname, verbose=verbose, fast_foward=True, update_submodules=False) else: return True except GitError: return False
def get_branch(self): if self.path_exists(): command = "hg branch --repository %s" % self.get_path() _, output, _ = run_shell_command(command, shell=True) if output is not None: return output.strip() return None
def _do_fetch(self, with_tags=False): """calls git fetch""" cmd = "git fetch" if with_tags: cmd += " --tags" value, _, _ = run_shell_command(cmd, cwd=self._path, shell=True, show_stdout=True) return value == 0
def get_version(self, spec=None): """ :param spec: (optional) token to identify desired version. For git, this may be anything accepted by git log, e.g. a tagname, branchname, or sha-id. :param fetch: When spec is given, can be used to suppress git fetch call :returns: current SHA-ID of the repository. Or if spec is provided, the SHA-ID of a commit specified by some token if found, else None """ if self.detect_presence(): command = "git log -1" if spec is not None: command += " %s" % sanitized(spec) command += " --format='%H'" repeated = False output = '' #we repeat the call once after fetching if necessary for _ in range(2): _, output, _ = run_shell_command(command, shell=True, cwd=self._path) if (output != '' or spec is None): break # we try again after fetching if given spec had not been found try: self._do_fetch() except GitError: return None # On Windows the version can have single quotes around it output = output.strip("'") return output return None
def get_affected_files(self, revision): cmd = "hg log -r %s --template \"{files}\"" % revision code, output, _ = run_shell_command(cmd, shell=True, cwd=self._path) affected = [] if code == 0: affected = output.split(" ") return affected
def get_log(self, relpath=None, limit=None): response = [] if relpath is None: relpath = "" # Compile regexes id_regex = re.compile("^revno: ([0-9]+)$", flags=re.MULTILINE) committer_regex = re.compile("^committer: (.+)$", flags=re.MULTILINE) timestamp_regex = re.compile("^timestamp: (.+)$", flags=re.MULTILINE) message_regex = re.compile("^ (.+)$", flags=re.MULTILINE) if self.path_exists() and os.path.exists(os.path.join(self._path, relpath)): # Get the log limit_cmd = ("--limit=%d" % (int(limit))) if limit else "" command = "bzr log %s %s" % (sanitized(relpath), limit_cmd) return_code, text_response, stderr = run_shell_command(command, shell=True, cwd=self._path) if return_code == 0: revno_match = id_regex.findall(text_response) committer_match = committer_regex.findall(text_response) timestamp_match = timestamp_regex.findall(text_response) message_match = message_regex.findall(text_response) # Extract the entries for revno, committer, timestamp, message in zip( revno_match, committer_match, timestamp_match, message_match ): author, email_address = email.utils.parseaddr(committer) date = dateutil.parser.parse(timestamp) log_data = {"id": revno, "author": author, "email": email_address, "message": message, "date": date} response.append(log_data) return response
def get_diff(self, basepath=None): response = '' if basepath is None: basepath = self._path if self.path_exists(): rel_path = normalized_rel_path(self._path, basepath) # git needs special treatment as it only works from inside # use HEAD to also show staged changes. Maybe should be option? # injection should be impossible using relpath, but to be sure, we check cmd = "git diff HEAD --src-prefix=%s/ --dst-prefix=%s/ ."%(sanitized(rel_path), sanitized(rel_path)) _, response, _ = run_shell_command(cmd, shell=True, cwd=self._path) if LooseVersion(self.gitversion) > LooseVersion('1.7'): cmd = 'git submodule foreach --recursive git diff HEAD' _, output, _ = run_shell_command(cmd, shell=True, cwd=self._path) response += _git_diff_path_submodule_change(output, rel_path) return response
def get_log(self, relpath=None, limit=None): response = [] if relpath == None: relpath = "" if self.path_exists() and os.path.exists(os.path.join(self._path, relpath)): # Get the log limit_cmd = ("-n %d" % (int(limit))) if limit else "" GIT_COMMIT_FIELDS = ["id", "author", "email", "date", "message"] GIT_LOG_FORMAT = "%x1f".join(["%H", "%an", "%ae", "%ad", "%s"]) + "%x1e" command = 'git --work-tree=%s log --format="%s" %s %s ' % ( self._path, GIT_LOG_FORMAT, limit_cmd, sanitized(relpath), ) return_code, response, stderr = run_shell_command(command, shell=True, cwd=self._path) if return_code == 0: # Parse response response = response.strip("\n\x1e").split("\x1e") response = [row.strip().split("\x1f") for row in response] response = [dict(zip(GIT_COMMIT_FIELDS, row)) for row in response] # Parse dates for entry in response: entry["date"] = dateutil.parser.parse(entry["date"]) return response
def get_log(self, relpath=None, limit=None): response = [] if relpath is None: relpath = '' if self.path_exists() and os.path.exists( os.path.join(self._path, relpath)): # Get the log limit_cmd = (("--limit %d" % (int(limit))) if limit else "") HG_COMMIT_FIELDS = ['id', 'author', 'email', 'date', 'message'] HG_LOG_FORMAT = '\x1f'.join([ '{node|short}', '{author|person}', '{autor|email}', '{date|isodate}', '{desc}' ]) + '\x1e' command = "hg log %s --template '%s' %s" % ( sanitized(relpath), HG_LOG_FORMAT, limit_cmd) return_code, response_str, stderr = run_shell_command( command, shell=True, cwd=self._path) if return_code == 0: # Parse response response = response_str.strip('\n\x1e').split("\x1e") response = [row.strip().split("\x1f") for row in response] response = [ dict(zip(HG_COMMIT_FIELDS, row)) for row in response ] # Parse dates for entry in response: entry['date'] = dateutil.parser.parse(entry['date']) return response
def get_log(self, relpath=None, limit=None): response = [] if relpath is None: relpath = '' if self.path_exists() and os.path.exists( os.path.join(self._path, relpath)): # Get the log limit_cmd = (("-n %d" % (int(limit))) if limit else "") GIT_COMMIT_FIELDS = ['id', 'author', 'email', 'date', 'message'] GIT_LOG_FORMAT = '%x1f'.join(['%H', '%an', '%ae', '%ad', '%s' ]) + '%x1e' command = "git --work-tree=%s log --format=\"%s\" %s %s " % ( self._path, GIT_LOG_FORMAT, limit_cmd, sanitized(relpath)) return_code, response_str, stderr = run_shell_command( command, shell=True, cwd=self._path) if return_code == 0: # Parse response response = response_str.strip('\n\x1e').split("\x1e") response = [row.strip().split("\x1f") for row in response] response = [ dict(zip(GIT_COMMIT_FIELDS, row)) for row in response ] # Parse dates for entry in response: entry['date'] = dateutil.parser.parse(entry['date']) return response
def get_log(self, relpath=None, limit=None): response = [] if relpath == None: relpath = '' if self.path_exists() and os.path.exists(os.path.join(self._path, relpath)): # Get the log limit_cmd = (("--limit %d" % (int(limit))) if limit else "") HG_COMMIT_FIELDS = ['id', 'author', 'email', 'date', 'message'] HG_LOG_FORMAT = '\x1f'.join(['{node|short}', '{author|person}', '{autor|email}', '{date|isodate}', '{desc}']) + '\x1e' command = "hg log %s --template '%s' %s" % (sanitized(relpath), HG_LOG_FORMAT, limit_cmd) return_code, response_str, stderr = run_shell_command(command, shell=True, cwd=self._path) if return_code == 0: # Parse response response = response_str.strip('\n\x1e').split("\x1e") response = [row.strip().split("\x1f") for row in response] response = [dict(zip(HG_COMMIT_FIELDS, row)) for row in response] print(response) # Parse dates for entry in response: entry['date'] = dateutil.parser.parse(entry['date']) return response
def get_version(self, spec=None): """ :param spec: (optional) token to identify desired version. For git, this may be anything accepted by git log, e.g. a tagname, branchname, or sha-id. :param fetch: When spec is given, can be used to suppress git fetch call :returns: current SHA-ID of the repository. Or if spec is provided, the SHA-ID of a commit specified by some token if found, else None """ if self.detect_presence(): command = "git log -1" if spec is not None: command += " %s" % sanitized(spec) command += " --format='%H'" output = '' #we repeat the call once after fetching if necessary for _ in range(2): _, output, _ = run_shell_command(command, shell=True, cwd=self._path) if (output != '' or spec is None): break # we try again after fetching if given spec had not been found try: self._do_fetch() except GitError: return None # On Windows the version can have single quotes around it output = output.strip("'") return output return None
def _rev_list_contains(self, refname, version, fetch=True): """ calls git rev-list with refname and returns True if version can be found in rev-list result :param refname: a git refname :param version: an SHA IDs (if partial, caller is responsible for mismatch) :returns: True if version is an ancestor commit from refname :raises: GitError when call to git fetch fails """ # to avoid listing unnecessarily many rev-ids, we cut off all # those we are definitely not interested in # $ git rev-list foo bar ^baz ^bez # means "list all the commits which are reachable from foo or # bar, but not from baz or bez". We use --parents because # ^baz also excludes baz itself. We could also use git # show --format=%P to get all parents first and use that, # not sure what's more performant if fetch: self._do_fetch() if (refname is not None and refname != '' and version is not None and version != ''): cmd = 'git rev-list %s ^%s --parents' % (sanitized(refname), sanitized(version)) _, output, _ = run_shell_command(cmd, shell=True, cwd=self._path) for line in output.splitlines(): # can have 1, 2 or 3 elements (commit, parent1, parent2) for hashid in line.split(" "): if hashid.startswith(version): return True return False
def get_log(self, relpath=None, limit=None): response = [] if relpath is None: relpath = '' if self.path_exists() and os.path.exists(os.path.join(self._path, relpath)): # Get the log limit_cmd = (("-n %d" % (int(limit))) if limit else "") GIT_COMMIT_FIELDS = ['id', 'author', 'email', 'date', 'message'] GIT_LOG_FORMAT = '%x1f'.join(['%H', '%an', '%ae', '%ad', '%s']) + '%x1e' command = "git --work-tree=%s log --format=\"%s\" %s %s " % (self._path, GIT_LOG_FORMAT, limit_cmd, sanitized(relpath)) return_code, response_str, stderr = run_shell_command(command, shell=True, cwd=self._path) if return_code == 0: # Parse response response = response_str.strip('\n\x1e').split("\x1e") response = [row.strip().split("\x1f") for row in response] response = [dict(zip(GIT_COMMIT_FIELDS, row)) for row in response] # Parse dates for entry in response: entry['date'] = dateutil.parser.parse(entry['date']) return response
def get_affected_files(self, revision): cmd = "hg log -r %s --template '{files}'" % revision code, output, _ = run_shell_command(cmd, shell=True, cwd=self._path) affected = [] if code == 0: affected = output.split(" ") return affected
def get_log(self, relpath=None, limit=None): response = [] if relpath == None: relpath = '' if self.path_exists() and os.path.exists(os.path.join(self._path, relpath)): # Get the log limit_cmd = (("--limit %d" % (int(limit))) if limit else "") command = "svn log %s --xml %s" % (limit_cmd, sanitized(relpath) if len(relpath) > 0 else '') return_code, xml_response, stderr = run_shell_command(command, shell=True, cwd=self._path) # Parse response dom = xml.dom.minidom.parseString(xml_response) log_entries = dom.getElementsByTagName("logentry") # Extract the entries for log_entry in log_entries: author_tag = log_entry.getElementsByTagName("author")[0] date_tag = log_entry.getElementsByTagName("date")[0] msg_tags = log_entry.getElementsByTagName("msg") log_data = dict() log_data['id'] = log_entry.getAttribute("revision") log_data['author'] = author_tag.firstChild.nodeValue log_data['email'] = None log_data['date'] = dateutil.parser.parse(str(date_tag.firstChild.nodeValue)) if len(msg_tags) > 0: log_data['message'] = msg_tags[0].firstChild.nodeValue response.append(log_data) return response
def get_log(self, relpath=None, limit=None): response = [] if relpath is None: relpath = '' if self.path_exists() and os.path.exists(os.path.join(self._path, relpath)): # Get the log limit_cmd = (("--limit %d" % (int(limit))) if limit else "") command = "svn log %s --xml %s" % (limit_cmd, sanitized(relpath) if len(relpath) > 0 else '') return_code, xml_response, stderr = run_shell_command(command, shell=True, cwd=self._path) # Parse response dom = xml.dom.minidom.parseString(xml_response) log_entries = dom.getElementsByTagName("logentry") # Extract the entries for log_entry in log_entries: author_tag = log_entry.getElementsByTagName("author")[0] date_tag = log_entry.getElementsByTagName("date")[0] msg_tags = log_entry.getElementsByTagName("msg") log_data = dict() log_data['id'] = log_entry.getAttribute("revision") log_data['author'] = author_tag.firstChild.nodeValue log_data['email'] = None log_data['date'] = dateutil.parser.parse(str(date_tag.firstChild.nodeValue)) if len(msg_tags) > 0 and msg_tags[0].firstChild: log_data['message'] = msg_tags[0].firstChild.nodeValue else: log_data['message'] = '' response.append(log_data) return response
def checkout(self, url, version=None, verbose=False, shallow=False, timeout=None): if url is None or url.strip() == '': raise ValueError('Invalid empty url : "%s"' % url) # bzr 2.5.1 fails if empty directory exists if not ensure_dir_notexists(self.get_path()): self.logger.error("Can't remove %s" % self.get_path()) return False cmd = 'bzr branch' if version: cmd += ' -r %s' % version cmd += ' %s %s' % (url, self._path) value, _, msg = run_shell_command(cmd, shell=True, show_stdout=verbose, verbose=verbose) if value != 0: if msg: self.logger.error('%s' % msg) return False return True
def rev_list_contains(self, refname, version, fetch=True): """ calls git rev-list with refname and returns True if version can be found in rev-list result :param refname: a git refname :param version: an SHA IDs (if partial, caller is responsible for mismatch) :returns: True if version is an ancestor commit from refname :raises: GitError when call to git fetch fails """ # to avoid listing unnecessarily many rev-ids, we cut off all # those we are definitely not interested in # $ git rev-list foo bar ^baz ^bez # means "list all the commits which are reachable from foo or # bar, but not from baz or bez". We use --parents because # ^baz also excludes baz itself. We could also use git # show --format=%P to get all parents first and use that, # not sure what's more performant if fetch: self._do_fetch() if (refname is not None and refname != '' and version is not None and version != ''): cmd = 'git rev-list %s ^%s --parents' % (sanitized(refname), sanitized(version)) _, output, _ = run_shell_command(cmd, shell=True, cwd=self._path) for line in output.splitlines(): # can have 1, 2 or 3 elements (commit, parent1, parent2) for hashid in line.split(" "): if hashid.startswith(version): return True return False
def get_version(self, spec=None): """ :param spec: (optional) token for identifying version. spec can be a whatever is allowed by 'hg log -r', e.g. a tagname, sha-ID, revision-number :returns: the current SHA-ID of the repository. Or if spec is provided, the SHA-ID of a revision specified by some token. """ # detect presence only if we need path for cwd in popen if spec is not None: if self.detect_presence(): command = 'hg log -r %s' % sanitized(spec) repeated = False output = '' # we repeat the call once after pullin if necessary while output == '': _, output, _ = run_shell_command(command, shell=True, cwd=self._path, us_env=True) if (output.strip() != '' and not output.startswith("abort") or repeated is True): matches = [ l for l in output.splitlines() if l.startswith('changeset: ') ] if len(matches) == 1: return matches[0].split(':')[2] else: sys.stderr.write( "Warning: found several candidates for hg spec %s" % spec) break self._do_pull() repeated = True return None else: command = 'hg identify -i %s' % self._path _, output, _ = run_shell_command(command, shell=True, us_env=True) if output is None or output.strip() == '' or output.startswith( "abort"): return None # hg adds a '+' to the end if there are uncommited # changes, inconsistent to hg log return output.strip().rstrip('+')
def get_branch(self): if self.path_exists(): _, output, _ = run_shell_command("git branch", shell=True, cwd=self._path) for line in output.splitlines(): elems = line.split() if len(elems) == 2 and elems[0] == "*": return elems[1] return None
def export_repository(self, version, basepath): # execute the bzr export cmd cmd = 'bzr export --format=tgz {0} '.format(basepath + '.tar.gz') cmd += '{0}'.format(version) result, _, _ = run_shell_command(cmd, shell=True, cwd=self._path) if result: return False return True
def get_diff(self, basepath=None): response = '' if basepath is None: basepath = self._path if self.path_exists(): rel_path = normalized_rel_path(self._path, basepath) # git needs special treatment as it only works from inside # use HEAD to also show staged changes. Maybe should be option? # injection should be impossible using relpath, but to be sure, we check cmd = "git diff HEAD --src-prefix=%s/ --dst-prefix=%s/ ." % \ (sanitized(rel_path), sanitized(rel_path)) _, response, _ = run_shell_command(cmd, shell=True, cwd=self._path) if LooseVersion(self.gitversion) > LooseVersion('1.7'): cmd = 'git submodule foreach --recursive git diff HEAD' _, output, _ = run_shell_command(cmd, shell=True, cwd=self._path) response += _git_diff_path_submodule_change(output, rel_path) return response
def do_work(self): command = self.command if not self.shell: command = shlex.split(command) _, stdout, stderr = run_shell_command(command, cwd=self.element.path, show_stdout=False, shell=self.shell) return {'stdout': stdout, 'stderr': stderr}
def get_url(self): """ :returns: GIT URL of the directory path (output of git info command), or None if it cannot be determined """ if self.detect_presence(): cmd = "git config --get remote.origin.url" _, output, _ = run_shell_command(cmd, shell=True, cwd=self._path) return output.rstrip() return None
def get_affected_files(self, revision): cmd = "svn diff --summarize -c {0}".format(revision) code, output, _ = run_shell_command(cmd, shell=True, cwd=self._path) affected = [] if code == 0: for filename in output.splitlines(): affected.append(filename.split(" ")[7]) return affected
def get_remote_contents(url): contents = [] if url: cmd = 'svn ls %s' % (url) result_code, output, _ = run_shell_command(cmd, shell=True) if result_code: return [] contents = [line.strip('/') for line in output.splitlines()] return contents
def get_url(self): """ :returns: git URL of the directory path (output of git info command), or None if it cannot be determined """ if self.detect_presence(): cmd = "git config --get remote.%s.url" % self._get_default_remote() _, output, _ = run_shell_command(cmd, shell=True, cwd=self._path) return output.rstrip() return None
def get_diff(self, basepath=None): response = None if basepath is None: basepath = self._path if self.path_exists(): rel_path = sanitized(normalized_rel_path(self._path, basepath)) command = "bzr diff %s" % rel_path command += " -p1 --prefix %s/:%s/" % (rel_path, rel_path) _, response, _ = run_shell_command(command, shell=True, cwd=basepath) return response
def _get_branch(self): if self.path_exists(): _, output, _ = run_shell_command('git branch', shell=True, cwd=self._path) for line in output.splitlines(): elems = line.split() if len(elems) == 2 and elems[0] == '*': return elems[1] return None
def get_affected_files(self, revision): cmd = "bzr status -c {0} -S -V".format(revision) code, output, _ = run_shell_command(cmd, shell=True, cwd=self._path) affected = [] if code == 0: for filename in output.splitlines(): affected.append(filename.split(" ")[2]) return affected
def get_diff(self, basepath=None): response = None if basepath is None: basepath = self._path if self.path_exists(): rel_path = normalized_rel_path(self._path, basepath) command = 'svn diff %s' % sanitized(rel_path) _, response, _ = run_shell_command(command, shell=True, cwd=basepath) return response
def _do_fetch(self): """ calls git fetch :raises: GitError when call fails """ cmd = "git fetch" value1, _, _ = run_shell_command(cmd, cwd=self._path, shell=True, no_filter=True, show_stdout=True) ## git fetch --tags ONLY fetches new tags and commits used, no other commits! cmd = "git fetch --tags" value2, _, _ = run_shell_command(cmd, cwd=self._path, shell=True, no_filter=True, show_stdout=True) if value1 != 0 or value2 != 0: raise GitError('git fetch failed')
def get_default_remote_version_label(self): if self.detect_presence(): _, output, _ = run_shell_command('git remote show %s' % self._get_default_remote(), shell=True, cwd=self._path) for line in output.splitlines(): elems = line.split() if elems[0:2] == ['HEAD', 'branch:']: return elems[2] return None