def test_capture_defaults(self, mockCommunicate): mockCommunicate.return_value = (('stdout', 'stderr'), 0) self.assertEqual('stdout', subprocess2.capture(['foo'], a=True)) mockCommunicate.assert_called_with(['foo'], a=True, stdin=subprocess2.DEVNULL, stdout=subprocess2.PIPE)
def AlertOnUncleanCheckout(): """Sends an alert if the cq is running live with local edits.""" diff = subprocess2.capture(['gclient', 'diff'], cwd=ROOT_DIR).strip() if diff: cq_alerts.SendAlert( 'CQ running with local diff.', ('Ruh-roh! Commit queue was started with an unclean checkout.\n\n' '$ gclient diff\n%s' % diff))
def getSVNInfo(url, revision): info = {} svn_info = subprocess2.capture( ['svn', 'info', '--non-interactive', '%s@%s' % (url, revision)], stderr=subprocess2.VOID).splitlines() for line in svn_info: match = re.search(r"(.*?):(.*)", line) if match: info[match.group(1).strip()] = match.group(2).strip() return info
def test_capture_defaults(self): results = self._fake_communicate() self.assertEquals('stdout', subprocess2.capture(['foo'], a=True)) expected = { 'args': ['foo'], 'a': True, 'stdin': subprocess2.VOID, 'stdout': subprocess2.PIPE, } self.assertEquals(expected, results)
def test_capture_defaults(self): results = self._fake_communicate() self.assertEquals( 'stdout', subprocess2.capture(['foo'], a=True)) expected = { 'args': ['foo'], 'a':True, 'stdin': subprocess2.VOID, 'stdout': subprocess2.PIPE, } self.assertEquals(expected, results)
def update(self, options, args, file_list): """Runs git to update or transparently checkout the working copy. All updated files will be appended to file_list. Raises: Error: if can't get URL for relative path. """ if args: raise gclient_utils.Error("Unsupported argument(s): %s" % ",".join(args)) self._CheckMinVersion("1.6.6") # If a dependency is not pinned, track the default remote branch. default_rev = 'refs/remotes/%s/master' % self.remote url, deps_revision = gclient_utils.SplitUrlRevision(self.url) revision = deps_revision managed = True if options.revision: # Override the revision number. revision = str(options.revision) if revision == 'unmanaged': # Check again for a revision in case an initial ref was specified # in the url, for example bla.git@refs/heads/custombranch revision = deps_revision managed = False if not revision: revision = default_rev if managed: self._DisableHooks() printed_path = False verbose = [] if options.verbose: self.Print('_____ %s at %s' % (self.relpath, revision), timestamp=False) verbose = ['--verbose'] printed_path = True remote_ref = scm.GIT.RefToRemoteRef(revision, self.remote) if remote_ref: # Rewrite remote refs to their local equivalents. revision = ''.join(remote_ref) rev_type = "branch" elif revision.startswith('refs/'): # Local branch? We probably don't want to support, since DEPS should # always specify branches as they are in the upstream repo. rev_type = "branch" else: # hash is also a tag, only make a distinction at checkout rev_type = "hash" mirror = self._GetMirror(url, options) if mirror: url = mirror.mirror_path # If we are going to introduce a new project, there is a possibility that # we are syncing back to a state where the project was originally a # sub-project rolled by DEPS (realistic case: crossing the Blink merge point # syncing backwards, when Blink was a DEPS entry and not part of src.git). # In such case, we might have a backup of the former .git folder, which can # be used to avoid re-fetching the entire repo again (useful for bisects). backup_dir = self.GetGitBackupDirPath() target_dir = os.path.join(self.checkout_path, '.git') if os.path.exists(backup_dir) and not os.path.exists(target_dir): gclient_utils.safe_makedirs(self.checkout_path) os.rename(backup_dir, target_dir) # Reset to a clean state self._Scrub('HEAD', options) if (not os.path.exists(self.checkout_path) or (os.path.isdir(self.checkout_path) and not os.path.exists(os.path.join(self.checkout_path, '.git')))): if mirror: self._UpdateMirrorIfNotContains(mirror, options, rev_type, revision) try: self._Clone(revision, url, options) except subprocess2.CalledProcessError: self._DeleteOrMove(options.force) self._Clone(revision, url, options) if file_list is not None: files = self._Capture(['ls-files']).splitlines() file_list.extend([os.path.join(self.checkout_path, f) for f in files]) if not verbose: # Make the output a little prettier. It's nice to have some whitespace # between projects when cloning. self.Print('') return self._Capture(['rev-parse', '--verify', 'HEAD']) if not managed: self._UpdateBranchHeads(options, fetch=False) self.Print('________ unmanaged solution; skipping %s' % self.relpath) return self._Capture(['rev-parse', '--verify', 'HEAD']) self._maybe_break_locks(options) if mirror: self._UpdateMirrorIfNotContains(mirror, options, rev_type, revision) # See if the url has changed (the unittests use git://foo for the url, let # that through). current_url = self._Capture(['config', 'remote.%s.url' % self.remote]) return_early = False # TODO(maruel): Delete url != 'git://foo' since it's just to make the # unit test pass. (and update the comment above) # Skip url auto-correction if remote.origin.gclient-auto-fix-url is set. # This allows devs to use experimental repos which have a different url # but whose branch(s) are the same as official repos. if (current_url.rstrip('/') != url.rstrip('/') and url != 'git://foo' and subprocess2.capture( ['git', 'config', 'remote.%s.gclient-auto-fix-url' % self.remote], cwd=self.checkout_path).strip() != 'False'): self.Print('_____ switching %s to a new upstream' % self.relpath) if not (options.force or options.reset): # Make sure it's clean self._CheckClean(revision) # Switch over to the new upstream self._Run(['remote', 'set-url', self.remote, url], options) if mirror: with open(os.path.join( self.checkout_path, '.git', 'objects', 'info', 'alternates'), 'w') as fh: fh.write(os.path.join(url, 'objects')) self._EnsureValidHeadObjectOrCheckout(revision, options, url) self._FetchAndReset(revision, file_list, options) return_early = True else: self._EnsureValidHeadObjectOrCheckout(revision, options, url) if return_early: return self._Capture(['rev-parse', '--verify', 'HEAD']) cur_branch = self._GetCurrentBranch() # Cases: # 0) HEAD is detached. Probably from our initial clone. # - make sure HEAD is contained by a named ref, then update. # Cases 1-4. HEAD is a branch. # 1) current branch is not tracking a remote branch # - try to rebase onto the new hash or branch # 2) current branch is tracking a remote branch with local committed # changes, but the DEPS file switched to point to a hash # - rebase those changes on top of the hash # 3) current branch is tracking a remote branch w/or w/out changes, and # no DEPS switch # - see if we can FF, if not, prompt the user for rebase, merge, or stop # 4) current branch is tracking a remote branch, but DEPS switches to a # different remote branch, and # a) current branch has no local changes, and --force: # - checkout new branch # b) current branch has local changes, and --force and --reset: # - checkout new branch # c) otherwise exit # GetUpstreamBranch returns something like 'refs/remotes/origin/master' for # a tracking branch # or 'master' if not a tracking branch (it's based on a specific rev/hash) # or it returns None if it couldn't find an upstream if cur_branch is None: upstream_branch = None current_type = "detached" logging.debug("Detached HEAD") else: upstream_branch = scm.GIT.GetUpstreamBranch(self.checkout_path) if not upstream_branch or not upstream_branch.startswith('refs/remotes'): current_type = "hash" logging.debug("Current branch is not tracking an upstream (remote)" " branch.") elif upstream_branch.startswith('refs/remotes'): current_type = "branch" else: raise gclient_utils.Error('Invalid Upstream: %s' % upstream_branch) if not scm.GIT.IsValidRevision(self.checkout_path, revision, sha_only=True): # Update the remotes first so we have all the refs. remote_output = scm.GIT.Capture(['remote'] + verbose + ['update'], cwd=self.checkout_path) if verbose: self.Print(remote_output) self._UpdateBranchHeads(options, fetch=True) revision = self._AutoFetchRef(options, revision) # This is a big hammer, debatable if it should even be here... if options.force or options.reset: target = 'HEAD' if options.upstream and upstream_branch: target = upstream_branch self._Scrub(target, options) if current_type == 'detached': # case 0 # We just did a Scrub, this is as clean as it's going to get. In # particular if HEAD is a commit that contains two versions of the same # file on a case-insensitive filesystem (e.g. 'a' and 'A'), there's no way # to actually "Clean" the checkout; that commit is uncheckoutable on this # system. The best we can do is carry forward to the checkout step. if not (options.force or options.reset): self._CheckClean(revision) self._CheckDetachedHead(revision, options) if self._Capture(['rev-list', '-n', '1', 'HEAD']) == revision: self.Print('Up-to-date; skipping checkout.') else: # 'git checkout' may need to overwrite existing untracked files. Allow # it only when nuclear options are enabled. self._Checkout( options, revision, force=(options.force and options.delete_unversioned_trees), quiet=True, ) if not printed_path: self.Print('_____ %s at %s' % (self.relpath, revision), timestamp=False) elif current_type == 'hash': # case 1 # Can't find a merge-base since we don't know our upstream. That makes # this command VERY likely to produce a rebase failure. For now we # assume origin is our upstream since that's what the old behavior was. upstream_branch = self.remote if options.revision or deps_revision: upstream_branch = revision self._AttemptRebase(upstream_branch, file_list, options, printed_path=printed_path, merge=options.merge) printed_path = True elif rev_type == 'hash': # case 2 self._AttemptRebase(upstream_branch, file_list, options, newbase=revision, printed_path=printed_path, merge=options.merge) printed_path = True elif remote_ref and ''.join(remote_ref) != upstream_branch: # case 4 new_base = ''.join(remote_ref) if not printed_path: self.Print('_____ %s at %s' % (self.relpath, revision), timestamp=False) switch_error = ("Could not switch upstream branch from %s to %s\n" % (upstream_branch, new_base) + "Please use --force or merge or rebase manually:\n" + "cd %s; git rebase %s\n" % (self.checkout_path, new_base) + "OR git checkout -b <some new branch> %s" % new_base) force_switch = False if options.force: try: self._CheckClean(revision) # case 4a force_switch = True except gclient_utils.Error as e: if options.reset: # case 4b force_switch = True else: switch_error = '%s\n%s' % (e.message, switch_error) if force_switch: self.Print("Switching upstream branch from %s to %s" % (upstream_branch, new_base)) switch_branch = 'gclient_' + remote_ref[1] self._Capture(['branch', '-f', switch_branch, new_base]) self._Checkout(options, switch_branch, force=True, quiet=True) else: # case 4c raise gclient_utils.Error(switch_error) else: # case 3 - the default case rebase_files = self._Capture( ['diff', upstream_branch, '--name-only']).split() if verbose: self.Print('Trying fast-forward merge to branch : %s' % upstream_branch) try: merge_args = ['merge'] if options.merge: merge_args.append('--ff') else: merge_args.append('--ff-only') merge_args.append(upstream_branch) merge_output = self._Capture(merge_args) except subprocess2.CalledProcessError as e: rebase_files = [] if re.match('fatal: Not possible to fast-forward, aborting.', e.stderr): if not printed_path: self.Print('_____ %s at %s' % (self.relpath, revision), timestamp=False) printed_path = True while True: if not options.auto_rebase: try: action = self._AskForData( 'Cannot %s, attempt to rebase? ' '(y)es / (q)uit / (s)kip : ' % ('merge' if options.merge else 'fast-forward merge'), options) except ValueError: raise gclient_utils.Error('Invalid Character') if options.auto_rebase or re.match(r'yes|y', action, re.I): self._AttemptRebase(upstream_branch, rebase_files, options, printed_path=printed_path, merge=False) printed_path = True break elif re.match(r'quit|q', action, re.I): raise gclient_utils.Error("Can't fast-forward, please merge or " "rebase manually.\n" "cd %s && git " % self.checkout_path + "rebase %s" % upstream_branch) elif re.match(r'skip|s', action, re.I): self.Print('Skipping %s' % self.relpath) return else: self.Print('Input not recognized') elif re.match("error: Your local changes to '.*' would be " "overwritten by merge. Aborting.\nPlease, commit your " "changes or stash them before you can merge.\n", e.stderr): if not printed_path: self.Print('_____ %s at %s' % (self.relpath, revision), timestamp=False) printed_path = True raise gclient_utils.Error(e.stderr) else: # Some other problem happened with the merge logging.error("Error during fast-forward merge in %s!" % self.relpath) self.Print(e.stderr) raise else: # Fast-forward merge was successful if not re.match('Already up-to-date.', merge_output) or verbose: if not printed_path: self.Print('_____ %s at %s' % (self.relpath, revision), timestamp=False) printed_path = True self.Print(merge_output.strip()) if not verbose: # Make the output a little prettier. It's nice to have some # whitespace between projects when syncing. self.Print('') if file_list is not None: file_list.extend( [os.path.join(self.checkout_path, f) for f in rebase_files]) # If the rebase generated a conflict, abort and ask user to fix if self._IsRebasing(): raise gclient_utils.Error('\n____ %s at %s\n' '\nConflict while rebasing this branch.\n' 'Fix the conflict and run gclient again.\n' 'See man git-rebase for details.\n' % (self.relpath, revision)) if verbose: self.Print('Checked out revision %s' % self.revinfo(options, (), None), timestamp=False) # If --reset and --delete_unversioned_trees are specified, remove any # untracked directories. if options.reset and options.delete_unversioned_trees: # GIT.CaptureStatus() uses 'dit diff' to compare to a specific SHA1 (the # merge-base by default), so doesn't include untracked files. So we use # 'git ls-files --directory --others --exclude-standard' here directly. paths = scm.GIT.Capture( ['ls-files', '--directory', '--others', '--exclude-standard'], self.checkout_path) for path in (p for p in paths.splitlines() if p.endswith('/')): full_path = os.path.join(self.checkout_path, path) if not os.path.islink(full_path): self.Print('_____ removing unversioned directory %s' % path) gclient_utils.rmtree(full_path) return self._Capture(['rev-parse', '--verify', 'HEAD'])