Remove SVN (and dcommit) support from git-cl

BUG=638750

Change-Id: I9ebe4ff861a37433209b66f4050370b49f17cdc7
Reviewed-on: https://chromium-review.googlesource.com/419661
Commit-Queue: Aaron Gable <agable@chromium.org>
Reviewed-by: Andrii Shyshkalov <tandrii@chromium.org>
diff --git a/git_cl.py b/git_cl.py
index 73d2bf9..c7a1fc5 100755
--- a/git_cl.py
+++ b/git_cl.py
@@ -66,9 +66,8 @@
 
 COMMIT_BOT_EMAIL = 'commit-bot@chromium.org'
 DEFAULT_SERVER = 'https://codereview.chromium.org'
-POSTUPSTREAM_HOOK_PATTERN = '.git/hooks/post-cl-%s'
+POSTUPSTREAM_HOOK = '.git/hooks/post-cl-land'
 DESCRIPTION_BACKUP_FILE = '~/.git_cl_description_backup'
-GIT_INSTRUCTIONS_URL = 'http://code.google.com/p/chromium/wiki/UsingGit'
 REFS_THAT_ALIAS_TO_OTHER_REFS = {
     'refs/remotes/origin/lkgr': 'refs/remotes/origin/master',
     'refs/remotes/origin/lkcr': 'refs/remotes/origin/master',
@@ -701,48 +700,6 @@
   write_json(output_file, converted)
 
 
-def MatchSvnGlob(url, base_url, glob_spec, allow_wildcards):
-  """Return the corresponding git ref if |base_url| together with |glob_spec|
-  matches the full |url|.
-
-  If |allow_wildcards| is true, |glob_spec| can contain wildcards (see below).
-  """
-  fetch_suburl, as_ref = glob_spec.split(':')
-  if allow_wildcards:
-    glob_match = re.match('(.+/)?(\*|{[^/]*})(/.+)?', fetch_suburl)
-    if glob_match:
-      # Parse specs like "branches/*/src:refs/remotes/svn/*" or
-      # "branches/{472,597,648}/src:refs/remotes/svn/*".
-      branch_re = re.escape(base_url)
-      if glob_match.group(1):
-        branch_re += '/' + re.escape(glob_match.group(1))
-      wildcard = glob_match.group(2)
-      if wildcard == '*':
-        branch_re += '([^/]*)'
-      else:
-        # Escape and replace surrounding braces with parentheses and commas
-        # with pipe symbols.
-        wildcard = re.escape(wildcard)
-        wildcard = re.sub('^\\\\{', '(', wildcard)
-        wildcard = re.sub('\\\\,', '|', wildcard)
-        wildcard = re.sub('\\\\}$', ')', wildcard)
-        branch_re += wildcard
-      if glob_match.group(3):
-        branch_re += re.escape(glob_match.group(3))
-      match = re.match(branch_re, url)
-      if match:
-        return re.sub('\*$', match.group(1), as_ref)
-
-  # Parse specs like "trunk/src:refs/remotes/origin/trunk".
-  if fetch_suburl:
-    full_url = base_url + '/' + fetch_suburl
-  else:
-    full_url = base_url
-  if full_url == url:
-    return as_ref
-  return None
-
-
 def print_stats(similarity, find_copies, args):
   """Prints statistics about the change to the user."""
   # --no-ext-diff is broken in some versions of Git, so try to work around
@@ -776,8 +733,6 @@
     self.default_server = None
     self.cc = None
     self.root = None
-    self.is_git_svn = None
-    self.svn_branch = None
     self.tree_status_url = None
     self.viewvc_url = None
     self.updated = False
@@ -839,87 +794,6 @@
       return mirror
     return None
 
-  def GetIsGitSvn(self):
-    """Return true if this repo looks like it's using git-svn."""
-    if self.is_git_svn is None:
-      if self.GetPendingRefPrefix():
-        # If PENDING_REF_PREFIX is set then it's a pure git repo no matter what.
-        self.is_git_svn = False
-      else:
-        # If you have any "svn-remote.*" config keys, we think you're using svn.
-        self.is_git_svn = RunGitWithCode(
-            ['config', '--local', '--get-regexp', r'^svn-remote\.'])[0] == 0
-    return self.is_git_svn
-
-  def GetSVNBranch(self):
-    if self.svn_branch is None:
-      if not self.GetIsGitSvn():
-        DieWithError('Repo doesn\'t appear to be a git-svn repo.')
-
-      # Try to figure out which remote branch we're based on.
-      # Strategy:
-      # 1) iterate through our branch history and find the svn URL.
-      # 2) find the svn-remote that fetches from the URL.
-
-      # regexp matching the git-svn line that contains the URL.
-      git_svn_re = re.compile(r'^\s*git-svn-id: (\S+)@', re.MULTILINE)
-
-      # We don't want to go through all of history, so read a line from the
-      # pipe at a time.
-      # The -100 is an arbitrary limit so we don't search forever.
-      cmd = ['git', 'log', '-100', '--pretty=medium']
-      proc = subprocess2.Popen(cmd, stdout=subprocess2.PIPE,
-                               env=GetNoGitPagerEnv())
-      url = None
-      for line in proc.stdout:
-        match = git_svn_re.match(line)
-        if match:
-          url = match.group(1)
-          proc.stdout.close()  # Cut pipe.
-          break
-
-      if url:
-        svn_remote_re = re.compile(r'^svn-remote\.([^.]+)\.url (.*)$')
-        remotes = RunGit(['config', '--get-regexp',
-                          r'^svn-remote\..*\.url']).splitlines()
-        for remote in remotes:
-          match = svn_remote_re.match(remote)
-          if match:
-            remote = match.group(1)
-            base_url = match.group(2)
-            rewrite_root = RunGit(
-                ['config', 'svn-remote.%s.rewriteRoot' % remote],
-                error_ok=True).strip()
-            if rewrite_root:
-              base_url = rewrite_root
-            fetch_spec = RunGit(
-                ['config', 'svn-remote.%s.fetch' % remote],
-                error_ok=True).strip()
-            if fetch_spec:
-              self.svn_branch = MatchSvnGlob(url, base_url, fetch_spec, False)
-              if self.svn_branch:
-                break
-            branch_spec = RunGit(
-                ['config', 'svn-remote.%s.branches' % remote],
-                error_ok=True).strip()
-            if branch_spec:
-              self.svn_branch = MatchSvnGlob(url, base_url, branch_spec, True)
-              if self.svn_branch:
-                break
-            tag_spec = RunGit(
-                ['config', 'svn-remote.%s.tags' % remote],
-                error_ok=True).strip()
-            if tag_spec:
-              self.svn_branch = MatchSvnGlob(url, base_url, tag_spec, True)
-              if self.svn_branch:
-                break
-
-      if not self.svn_branch:
-        DieWithError('Can\'t guess svn branch -- try specifying it on the '
-            'command line')
-
-    return self.svn_branch
-
   def GetTreeStatusUrl(self, error_ok=False):
     if not self.tree_status_url:
       error_message = ('You must configure your tree status URL by running '
@@ -1397,28 +1271,19 @@
       if upstream_branch:
         remote = RunGit(['config', 'rietveld.upstream-remote']).strip()
       else:
-        # Fall back on trying a git-svn upstream branch.
-        if settings.GetIsGitSvn():
-          upstream_branch = settings.GetSVNBranch()
+        # Else, try to guess the origin remote.
+        remote_branches = RunGit(['branch', '-r']).split()
+        if 'origin/master' in remote_branches:
+          # Fall back on origin/master if it exits.
+          remote = 'origin'
+          upstream_branch = 'refs/heads/master'
         else:
-          # Else, try to guess the origin remote.
-          remote_branches = RunGit(['branch', '-r']).split()
-          if 'origin/master' in remote_branches:
-            # Fall back on origin/master if it exits.
-            remote = 'origin'
-            upstream_branch = 'refs/heads/master'
-          elif 'origin/trunk' in remote_branches:
-            # Fall back on origin/trunk if it exists. Generally a shared
-            # git-svn clone
-            remote = 'origin'
-            upstream_branch = 'refs/heads/trunk'
-          else:
-            DieWithError(
-               'Unable to determine default branch to diff against.\n'
-               'Either pass complete "git diff"-style arguments, like\n'
-               '  git cl upload origin/master\n'
-               'or verify this branch is set up to track another \n'
-               '(via the --track argument to "git checkout -b ...").')
+          DieWithError(
+             'Unable to determine default branch to diff against.\n'
+             'Either pass complete "git diff"-style arguments, like\n'
+             '  git cl upload origin/master\n'
+             'or verify this branch is set up to track another \n'
+             '(via the --track argument to "git checkout -b ...").')
 
     return remote, upstream_branch
 
@@ -1457,17 +1322,11 @@
           remote, = remotes
         elif 'origin' in remotes:
           remote = 'origin'
-          logging.warning('Could not determine which remote this change is '
-                          'associated with, so defaulting to "%s".  This may '
-                          'not be what you want.  You may prevent this message '
-                          'by running "git svn info" as documented here:  %s',
-                          self._remote,
-                          GIT_INSTRUCTIONS_URL)
+          logging.warn('Could not determine which remote this change is '
+                       'associated with, so defaulting to "%s".' % self._remote)
         else:
           logging.warn('Could not determine which remote this change is '
-                       'associated with.  You may prevent this message by '
-                       'running "git svn info" as documented here:  %s',
-                       GIT_INSTRUCTIONS_URL)
+                       'associated with.')
         branch = 'HEAD'
       if branch.startswith('refs/remotes'):
         self._remote = (remote, branch)
@@ -1526,19 +1385,6 @@
     """
     return self._GitGetBranchConfigValue('base-url')
 
-  def GetGitSvnRemoteUrl(self):
-    """Return the configured git-svn remote URL parsed from git svn info.
-
-    Returns None if it is not set.
-    """
-    # URL is dependent on the current directory.
-    data = RunGit(['svn', 'info'], cwd=settings.GetRoot())
-    if data:
-      keys = dict(line.split(': ', 1) for line in data.splitlines()
-                  if ': ' in line)
-      return keys.get('URL', None)
-    return None
-
   def GetRemoteUrl(self):
     """Return the configured remote URL, e.g. 'git://example.org/foo.git/'.
 
@@ -2293,8 +2139,12 @@
     else:
       if options.title is not None:
         upload_args.extend(['--title', options.title])
-      message = (options.title or options.message or
-                 CreateDescriptionFromLog(args))
+      if options.message:
+        message = options.message
+      else:
+        message = CreateDescriptionFromLog(args)
+        if options.title:
+          message = options.title + '\n\n' + message
       change_desc = ChangeDescription(message)
       if options.reviewers or options.tbr_owners:
         change_desc.update_reviewers(options.reviewers,
@@ -2343,12 +2193,9 @@
     # projects that have their source spread across multiple repos.
     remote_url = self.GetGitBaseUrlFromConfig()
     if not remote_url:
-      if settings.GetIsGitSvn():
-        remote_url = self.GetGitSvnRemoteUrl()
-      else:
-        if self.GetRemoteUrl() and '/' in self.GetUpstreamBranch():
-          remote_url = '%s@%s' % (self.GetRemoteUrl(),
-                                  self.GetUpstreamBranch().split('/')[-1])
+      if self.GetRemoteUrl() and '/' in self.GetUpstreamBranch():
+        remote_url = '%s@%s' % (self.GetRemoteUrl(),
+                                self.GetUpstreamBranch().split('/')[-1])
     if remote_url:
       remote, remote_branch = self.GetRemoteBranch()
       target_ref = GetTargetRef(remote, remote_branch, options.target_branch,
@@ -4209,7 +4056,7 @@
 def CMDpresubmit(parser, args):
   """Runs presubmit tests on the current changelist."""
   parser.add_option('-u', '--upload', action='store_true',
-                    help='Run upload hook instead of the push/dcommit hook')
+                    help='Run upload hook instead of the push hook')
   parser.add_option('-f', '--force', action='store_true',
                     help='Run checks even if tree is dirty')
   auth.add_auth_options(parser)
@@ -4354,14 +4201,15 @@
                     help='bypass watchlists auto CC-ing reviewers')
   parser.add_option('-f', action='store_true', dest='force',
                     help="force yes to questions (don't prompt)")
-  parser.add_option('-m', dest='message', help='message for patchset')
+  parser.add_option('--message', '-m', dest='message',
+                    help='message for patchset')
   parser.add_option('-b', '--bug',
                     help='pre-populate the bug number(s) for this issue. '
                          'If several, separate with commas')
   parser.add_option('--message-file', dest='message_file',
                     help='file which contains message for patchset')
-  parser.add_option('-t', dest='title',
-                    help='title for patchset (Rietveld only)')
+  parser.add_option('--title', '-t', dest='title',
+                    help='title for patchset')
   parser.add_option('-r', '--reviewers',
                     action='append', default=[],
                     help='reviewer email addresses')
@@ -4433,366 +4281,6 @@
   return cl.CMDUpload(options, args, orig_args)
 
 
-def IsSubmoduleMergeCommit(ref):
-  # When submodules are added to the repo, we expect there to be a single
-  # non-git-svn merge commit at remote HEAD with a signature comment.
-  pattern = '^SVN changes up to revision [0-9]*$'
-  cmd = ['rev-list', '--merges', '--grep=%s' % pattern, '%s^!' % ref]
-  return RunGit(cmd) != ''
-
-
-def SendUpstream(parser, args, cmd):
-  """Common code for CMDland and CmdDCommit
-
-  In case of Gerrit, uses Gerrit REST api to "submit" the issue, which pushes
-  upstream and closes the issue automatically and atomically.
-
-  Otherwise (in case of Rietveld):
-    Squashes branch into a single commit.
-    Updates commit message with metadata (e.g. pointer to review).
-    Pushes the code upstream.
-    Updates review and closes.
-  """
-  parser.add_option('--bypass-hooks', action='store_true', dest='bypass_hooks',
-                    help='bypass upload presubmit hook')
-  parser.add_option('-m', dest='message',
-                    help="override review description")
-  parser.add_option('-f', action='store_true', dest='force',
-                    help="force yes to questions (don't prompt)")
-  parser.add_option('-c', dest='contributor',
-                    help="external contributor for patch (appended to " +
-                         "description and used as author for git). Should be " +
-                         "formatted as 'First Last <email@example.com>'")
-  add_git_similarity(parser)
-  auth.add_auth_options(parser)
-  (options, args) = parser.parse_args(args)
-  auth_config = auth.extract_auth_config_from_options(options)
-
-  cl = Changelist(auth_config=auth_config)
-
-  # TODO(tandrii): refactor this into _RietveldChangelistImpl method.
-  if cl.IsGerrit():
-    if options.message:
-      # This could be implemented, but it requires sending a new patch to
-      # Gerrit, as Gerrit unlike Rietveld versions messages with patchsets.
-      # Besides, Gerrit has the ability to change the commit message on submit
-      # automatically, thus there is no need to support this option (so far?).
-      parser.error('-m MESSAGE option is not supported for Gerrit.')
-    if options.contributor:
-      parser.error(
-          '-c CONTRIBUTOR option is not supported for Gerrit.\n'
-          'Before uploading a commit to Gerrit, ensure it\'s author field is '
-          'the contributor\'s "name <email>". If you can\'t upload such a '
-          'commit for review, contact your repository admin and request'
-          '"Forge-Author" permission.')
-    if not cl.GetIssue():
-      DieWithError('You must upload the change first to Gerrit.\n'
-                   '  If you would rather have `git cl land` upload '
-                   'automatically for you, see http://crbug.com/642759')
-    return cl._codereview_impl.CMDLand(options.force, options.bypass_hooks,
-                                       options.verbose)
-
-  current = cl.GetBranch()
-  remote, upstream_branch = cl.FetchUpstreamTuple(cl.GetBranch())
-  if not settings.GetIsGitSvn() and remote == '.':
-    print()
-    print('Attempting to push branch %r into another local branch!' % current)
-    print()
-    print('Either reparent this branch on top of origin/master:')
-    print('  git reparent-branch --root')
-    print()
-    print('OR run `git rebase-update` if you think the parent branch is ')
-    print('already committed.')
-    print()
-    print('  Current parent: %r' % upstream_branch)
-    return 1
-
-  if not args or cmd == 'land':
-    # Default to merging against our best guess of the upstream branch.
-    args = [cl.GetUpstreamBranch()]
-
-  if options.contributor:
-    if not re.match('^.*\s<\S+@\S+>$', options.contributor):
-      print("Please provide contibutor as 'First Last <email@example.com>'")
-      return 1
-
-  base_branch = args[0]
-  base_has_submodules = IsSubmoduleMergeCommit(base_branch)
-
-  if git_common.is_dirty_git_tree(cmd):
-    return 1
-
-  # This rev-list syntax means "show all commits not in my branch that
-  # are in base_branch".
-  upstream_commits = RunGit(['rev-list', '^' + cl.GetBranchRef(),
-                             base_branch]).splitlines()
-  if upstream_commits:
-    print('Base branch "%s" has %d commits '
-          'not in this branch.' % (base_branch, len(upstream_commits)))
-    print('Run "git merge %s" before attempting to %s.' % (base_branch, cmd))
-    return 1
-
-  # This is the revision `svn dcommit` will commit on top of.
-  svn_head = None
-  if cmd == 'dcommit' or base_has_submodules:
-    svn_head = RunGit(['log', '--grep=^git-svn-id:', '-1',
-                       '--pretty=format:%H'])
-
-  if cmd == 'dcommit':
-    # If the base_head is a submodule merge commit, the first parent of the
-    # base_head should be a git-svn commit, which is what we're interested in.
-    base_svn_head = base_branch
-    if base_has_submodules:
-      base_svn_head += '^1'
-
-    extra_commits = RunGit(['rev-list', '^' + svn_head, base_svn_head])
-    if extra_commits:
-      print('This branch has %d additional commits not upstreamed yet.'
-            % len(extra_commits.splitlines()))
-      print('Upstream "%s" or rebase this branch on top of the upstream trunk '
-            'before attempting to %s.' % (base_branch, cmd))
-      return 1
-
-  merge_base = RunGit(['merge-base', base_branch, 'HEAD']).strip()
-  if not options.bypass_hooks:
-    author = None
-    if options.contributor:
-      author = re.search(r'\<(.*)\>', options.contributor).group(1)
-    hook_results = cl.RunHook(
-        committing=True,
-        may_prompt=not options.force,
-        verbose=options.verbose,
-        change=cl.GetChange(merge_base, author))
-    if not hook_results.should_continue():
-      return 1
-
-    # Check the tree status if the tree status URL is set.
-    status = GetTreeStatus()
-    if 'closed' == status:
-      print('The tree is closed.  Please wait for it to reopen. Use '
-            '"git cl %s --bypass-hooks" to commit on a closed tree.' % cmd)
-      return 1
-    elif 'unknown' == status:
-      print('Unable to determine tree status.  Please verify manually and '
-            'use "git cl %s --bypass-hooks" to commit on a closed tree.' % cmd)
-      return 1
-
-  change_desc = ChangeDescription(options.message)
-  if not change_desc.description and cl.GetIssue():
-    change_desc = ChangeDescription(cl.GetDescription())
-
-  if not change_desc.description:
-    if not cl.GetIssue() and options.bypass_hooks:
-      change_desc = ChangeDescription(CreateDescriptionFromLog([merge_base]))
-    else:
-      print('No description set.')
-      print('Visit %s/edit to set it.' % (cl.GetIssueURL()))
-      return 1
-
-  # Keep a separate copy for the commit message, because the commit message
-  # contains the link to the Rietveld issue, while the Rietveld message contains
-  # the commit viewvc url.
-  if cl.GetIssue():
-    change_desc.update_reviewers(cl.GetApprovingReviewers())
-
-  commit_desc = ChangeDescription(change_desc.description)
-  if cl.GetIssue():
-    # Xcode won't linkify this URL unless there is a non-whitespace character
-    # after it. Add a period on a new line to circumvent this. Also add a space
-    # before the period to make sure that Gitiles continues to correctly resolve
-    # the URL.
-    commit_desc.append_footer('Review-Url: %s .' % cl.GetIssueURL())
-  if options.contributor:
-    commit_desc.append_footer('Patch from %s.' % options.contributor)
-
-  print('Description:')
-  print(commit_desc.description)
-
-  branches = [merge_base, cl.GetBranchRef()]
-  if not options.force:
-    print_stats(options.similarity, options.find_copies, branches)
-
-  # We want to squash all this branch's commits into one commit with the proper
-  # description. We do this by doing a "reset --soft" to the base branch (which
-  # keeps the working copy the same), then dcommitting that.  If origin/master
-  # has a submodule merge commit, we'll also need to cherry-pick the squashed
-  # commit onto a branch based on the git-svn head.
-  MERGE_BRANCH = 'git-cl-commit'
-  CHERRY_PICK_BRANCH = 'git-cl-cherry-pick'
-  # Delete the branches if they exist.
-  for branch in [MERGE_BRANCH, CHERRY_PICK_BRANCH]:
-    showref_cmd = ['show-ref', '--quiet', '--verify', 'refs/heads/%s' % branch]
-    result = RunGitWithCode(showref_cmd)
-    if result[0] == 0:
-      RunGit(['branch', '-D', branch])
-
-  # We might be in a directory that's present in this branch but not in the
-  # trunk.  Move up to the top of the tree so that git commands that expect a
-  # valid CWD won't fail after we check out the merge branch.
-  rel_base_path = settings.GetRelativeRoot()
-  if rel_base_path:
-    os.chdir(rel_base_path)
-
-  # Stuff our change into the merge branch.
-  # We wrap in a try...finally block so if anything goes wrong,
-  # we clean up the branches.
-  retcode = -1
-  pushed_to_pending = False
-  pending_ref = None
-  revision = None
-  try:
-    RunGit(['checkout', '-q', '-b', MERGE_BRANCH])
-    RunGit(['reset', '--soft', merge_base])
-    if options.contributor:
-      RunGit(
-          [
-            'commit', '--author', options.contributor,
-            '-m', commit_desc.description,
-          ])
-    else:
-      RunGit(['commit', '-m', commit_desc.description])
-    if base_has_submodules:
-      cherry_pick_commit = RunGit(['rev-list', 'HEAD^!']).rstrip()
-      RunGit(['branch', CHERRY_PICK_BRANCH, svn_head])
-      RunGit(['checkout', CHERRY_PICK_BRANCH])
-      RunGit(['cherry-pick', cherry_pick_commit])
-    if cmd == 'land':
-      remote, branch = cl.FetchUpstreamTuple(cl.GetBranch())
-      logging.debug('remote: %s, branch %s', remote, branch)
-      mirror = settings.GetGitMirror(remote)
-      if mirror:
-        pushurl = mirror.url
-        git_numberer = _GitNumbererState.load(pushurl, branch)
-      else:
-        pushurl = remote  # Usually, this is 'origin'.
-        git_numberer = _GitNumbererState.load(
-            RunGit(['config', 'remote.%s.url' % remote]).strip(), branch)
-
-      pending_prefix = git_numberer.pending_prefix
-
-      if git_numberer.should_add_git_number:
-        # TODO(tandrii): run git fetch in a loop + autorebase when there there
-        # is no pending ref to push to?
-        logging.debug('Adding git number footers')
-        parent_msg = RunGit(['show', '-s', '--format=%B', merge_base]).strip()
-        commit_desc.update_with_git_number_footers(merge_base, parent_msg,
-                                                   branch)
-        # Ensure timestamps are monotonically increasing.
-        timestamp = max(1 + _get_committer_timestamp(merge_base),
-                        _get_committer_timestamp('HEAD'))
-        _git_amend_head(commit_desc.description, timestamp)
-        change_desc = ChangeDescription(commit_desc.description)
-        # If gnumbd is sitll ON and we ultimately push to branch with
-        # pending_prefix, gnumbd will modify footers we've just inserted with
-        # 'Original-', which is annoying but still technically correct.
-
-      if not pending_prefix or branch.startswith(pending_prefix):
-        # If not using refs/pending/heads/* at all, or target ref is already set
-        # to pending, then push to the target ref directly.
-        # NB(tandrii): I think branch.startswith(pending_prefix) never happens
-        # in practise. I really tried to create a new branch tracking
-        # refs/pending/heads/master directly and git cl land failed long before
-        # reaching this. Disagree? Comment on http://crbug.com/642493.
-        if pending_prefix:
-          print('\n\nYOU GOT A CHANCE TO WIN A FREE GIFT!\n\n'
-                'Grab your .git/config, add instructions how to reproduce '
-                'this, and post it to http://crbug.com/642493.\n'
-                'The first reporter gets a free "Black Swan" book from '
-                'tandrii@\n\n')
-        retcode, output = RunGitWithCode(
-            ['push', '--porcelain', pushurl, 'HEAD:%s' % branch])
-        pushed_to_pending = pending_prefix and branch.startswith(pending_prefix)
-      else:
-        # Cherry-pick the change on top of pending ref and then push it.
-        assert branch.startswith('refs/'), branch
-        assert pending_prefix[-1] == '/', pending_prefix
-        pending_ref = pending_prefix + branch[len('refs/'):]
-        retcode, output = PushToGitPending(pushurl, pending_ref)
-        pushed_to_pending = (retcode == 0)
-      if retcode == 0:
-        revision = RunGit(['rev-parse', 'HEAD']).strip()
-    else:
-      # dcommit the merge branch.
-      cmd_args = [
-        'svn', 'dcommit',
-        '-C%s' % options.similarity,
-        '--no-rebase', '--rmdir',
-      ]
-      if settings.GetForceHttpsCommitUrl():
-        # Allow forcing https commit URLs for some projects that don't allow
-        # committing to http URLs (like Google Code).
-        remote_url = cl.GetGitSvnRemoteUrl()
-        if urlparse.urlparse(remote_url).scheme == 'http':
-          remote_url = remote_url.replace('http://', 'https://')
-        cmd_args.append('--commit-url=%s' % remote_url)
-      _, output = RunGitWithCode(cmd_args)
-      if 'Committed r' in output:
-        revision = re.match(
-          '.*?\nCommitted r(\\d+)', output, re.DOTALL).group(1)
-    logging.debug(output)
-  except:  # pylint: disable=bare-except
-    if _IS_BEING_TESTED:
-      logging.exception('this is likely your ACTUAL cause of test failure.\n'
-                        + '-' * 30 + '8<' + '-' * 30)
-      logging.error('\n' + '-' * 30 + '8<' + '-' * 30 + '\n\n\n')
-    raise
-  finally:
-    # And then swap back to the original branch and clean up.
-    RunGit(['checkout', '-q', cl.GetBranch()])
-    RunGit(['branch', '-D', MERGE_BRANCH])
-    if base_has_submodules:
-      RunGit(['branch', '-D', CHERRY_PICK_BRANCH])
-
-  if not revision:
-    print('Failed to push. If this persists, please file a bug.')
-    return 1
-
-  killed = False
-  if pushed_to_pending:
-    try:
-      revision = WaitForRealCommit(remote, revision, base_branch, branch)
-      # We set pushed_to_pending to False, since it made it all the way to the
-      # real ref.
-      pushed_to_pending = False
-    except KeyboardInterrupt:
-      killed = True
-
-  if cl.GetIssue():
-    to_pending = ' to pending queue' if pushed_to_pending else ''
-    viewvc_url = settings.GetViewVCUrl()
-    if not to_pending:
-      if viewvc_url and revision:
-        change_desc.append_footer(
-            'Committed: %s%s' % (viewvc_url, revision))
-      elif revision:
-        change_desc.append_footer('Committed: %s' % (revision,))
-    print('Closing issue '
-          '(you may be prompted for your codereview password)...')
-    cl.UpdateDescription(change_desc.description)
-    cl.CloseIssue()
-    props = cl.GetIssueProperties()
-    patch_num = len(props['patchsets'])
-    comment = "Committed patchset #%d (id:%d)%s manually as %s" % (
-        patch_num, props['patchsets'][-1], to_pending, revision)
-    if options.bypass_hooks:
-      comment += ' (tree was closed).' if GetTreeStatus() == 'closed' else '.'
-    else:
-      comment += ' (presubmit successful).'
-    cl.RpcServer().add_comment(cl.GetIssue(), comment)
-
-  if pushed_to_pending:
-    _, branch = cl.FetchUpstreamTuple(cl.GetBranch())
-    print('The commit is in the pending queue (%s).' % pending_ref)
-    print('It will show up on %s in ~1 min, once it gets a Cr-Commit-Position '
-          'footer.' % branch)
-
-  hook = POSTUPSTREAM_HOOK_PATTERN % cmd
-  if os.path.isfile(hook):
-    RunCommand([hook, merge_base], error_ok=True)
-
-  return 1 if killed else 0
-
-
 def WaitForRealCommit(remote, pushed_commit, local_base_ref, real_ref):
   print()
   print('Waiting for commit to be landed on %s...' % real_ref)
@@ -4886,41 +4374,315 @@
   return '(prohibited by Gerrit)' in push_stdout
 
 
-@subcommand.usage('[upstream branch to apply against]')
+@subcommand.usage('DEPRECATED')
 def CMDdcommit(parser, args):
-  """Commits the current changelist via git-svn."""
-  if not settings.GetIsGitSvn():
-    if git_footers.get_footer_svn_id():
-      # If it looks like previous commits were mirrored with git-svn.
-      message = """This repository appears to be a git-svn mirror, but we
-don't support git-svn mirrors anymore."""
-    else:
-      message = """This doesn't appear to be an SVN repository.
-If your project has a true, writeable git repository, you probably want to run
-'git cl land' instead.
-If your project has a git mirror of an upstream SVN master, you probably need
-to run 'git svn init'.
-
-Using the wrong command might cause your commit to appear to succeed, and the
-review to be closed, without actually landing upstream. If you choose to
-proceed, please verify that the commit lands upstream as expected."""
-    print(message)
-    ask_for_data('[Press enter to dcommit or ctrl-C to quit]')
-  print('WARNING: chrome infrastructure is migrating SVN repos to Git.\n'
-        'Please let us know of this project you are committing to:'
-        '    http://crbug.com/600451')
-  return SendUpstream(parser, args, 'dcommit')
+  """DEPRECATED: Used to commit the current changelist via git-svn."""
+  message = ('git-cl no longer supports committing to SVN repositories via '
+             'git-svn. You probably want to use `git cl land` instead.')
+  print(message)
+  return 1
 
 
 @subcommand.usage('[upstream branch to apply against]')
 def CMDland(parser, args):
-  """Commits the current changelist via git."""
-  if settings.GetIsGitSvn() or git_footers.get_footer_svn_id():
-    print('This appears to be an SVN repository.')
-    print('Are you sure you didn\'t mean \'git cl dcommit\'?')
-    print('(Ignore if this is the first commit after migrating from svn->git)')
-    ask_for_data('[Press enter to push or ctrl-C to quit]')
-  return SendUpstream(parser, args, 'land')
+  """Commits the current changelist via git.
+
+  In case of Gerrit, uses Gerrit REST api to "submit" the issue, which pushes
+  upstream and closes the issue automatically and atomically.
+
+  Otherwise (in case of Rietveld):
+    Squashes branch into a single commit.
+    Updates commit message with metadata (e.g. pointer to review).
+    Pushes the code upstream.
+    Updates review and closes.
+  """
+  parser.add_option('--bypass-hooks', action='store_true', dest='bypass_hooks',
+                    help='bypass upload presubmit hook')
+  parser.add_option('-m', dest='message',
+                    help="override review description")
+  parser.add_option('-f', action='store_true', dest='force',
+                    help="force yes to questions (don't prompt)")
+  parser.add_option('-c', dest='contributor',
+                    help="external contributor for patch (appended to " +
+                         "description and used as author for git). Should be " +
+                         "formatted as 'First Last <email@example.com>'")
+  add_git_similarity(parser)
+  auth.add_auth_options(parser)
+  (options, args) = parser.parse_args(args)
+  auth_config = auth.extract_auth_config_from_options(options)
+
+  cl = Changelist(auth_config=auth_config)
+
+  # TODO(tandrii): refactor this into _RietveldChangelistImpl method.
+  if cl.IsGerrit():
+    if options.message:
+      # This could be implemented, but it requires sending a new patch to
+      # Gerrit, as Gerrit unlike Rietveld versions messages with patchsets.
+      # Besides, Gerrit has the ability to change the commit message on submit
+      # automatically, thus there is no need to support this option (so far?).
+      parser.error('-m MESSAGE option is not supported for Gerrit.')
+    if options.contributor:
+      parser.error(
+          '-c CONTRIBUTOR option is not supported for Gerrit.\n'
+          'Before uploading a commit to Gerrit, ensure it\'s author field is '
+          'the contributor\'s "name <email>". If you can\'t upload such a '
+          'commit for review, contact your repository admin and request'
+          '"Forge-Author" permission.')
+    if not cl.GetIssue():
+      DieWithError('You must upload the change first to Gerrit.\n'
+                   '  If you would rather have `git cl land` upload '
+                   'automatically for you, see http://crbug.com/642759')
+    return cl._codereview_impl.CMDLand(options.force, options.bypass_hooks,
+                                       options.verbose)
+
+  current = cl.GetBranch()
+  remote, upstream_branch = cl.FetchUpstreamTuple(cl.GetBranch())
+  if remote == '.':
+    print()
+    print('Attempting to push branch %r into another local branch!' % current)
+    print()
+    print('Either reparent this branch on top of origin/master:')
+    print('  git reparent-branch --root')
+    print()
+    print('OR run `git rebase-update` if you think the parent branch is ')
+    print('already committed.')
+    print()
+    print('  Current parent: %r' % upstream_branch)
+    return 1
+
+  if not args:
+    # Default to merging against our best guess of the upstream branch.
+    args = [cl.GetUpstreamBranch()]
+
+  if options.contributor:
+    if not re.match('^.*\s<\S+@\S+>$', options.contributor):
+      print("Please provide contibutor as 'First Last <email@example.com>'")
+      return 1
+
+  base_branch = args[0]
+
+  if git_common.is_dirty_git_tree('land'):
+    return 1
+
+  # This rev-list syntax means "show all commits not in my branch that
+  # are in base_branch".
+  upstream_commits = RunGit(['rev-list', '^' + cl.GetBranchRef(),
+                             base_branch]).splitlines()
+  if upstream_commits:
+    print('Base branch "%s" has %d commits '
+          'not in this branch.' % (base_branch, len(upstream_commits)))
+    print('Run "git merge %s" before attempting to land.' % base_branch)
+    return 1
+
+  merge_base = RunGit(['merge-base', base_branch, 'HEAD']).strip()
+  if not options.bypass_hooks:
+    author = None
+    if options.contributor:
+      author = re.search(r'\<(.*)\>', options.contributor).group(1)
+    hook_results = cl.RunHook(
+        committing=True,
+        may_prompt=not options.force,
+        verbose=options.verbose,
+        change=cl.GetChange(merge_base, author))
+    if not hook_results.should_continue():
+      return 1
+
+    # Check the tree status if the tree status URL is set.
+    status = GetTreeStatus()
+    if 'closed' == status:
+      print('The tree is closed.  Please wait for it to reopen. Use '
+            '"git cl land --bypass-hooks" to commit on a closed tree.')
+      return 1
+    elif 'unknown' == status:
+      print('Unable to determine tree status.  Please verify manually and '
+            'use "git cl land --bypass-hooks" to commit on a closed tree.')
+      return 1
+
+  change_desc = ChangeDescription(options.message)
+  if not change_desc.description and cl.GetIssue():
+    change_desc = ChangeDescription(cl.GetDescription())
+
+  if not change_desc.description:
+    if not cl.GetIssue() and options.bypass_hooks:
+      change_desc = ChangeDescription(CreateDescriptionFromLog([merge_base]))
+    else:
+      print('No description set.')
+      print('Visit %s/edit to set it.' % (cl.GetIssueURL()))
+      return 1
+
+  # Keep a separate copy for the commit message, because the commit message
+  # contains the link to the Rietveld issue, while the Rietveld message contains
+  # the commit viewvc url.
+  if cl.GetIssue():
+    change_desc.update_reviewers(cl.GetApprovingReviewers())
+
+  commit_desc = ChangeDescription(change_desc.description)
+  if cl.GetIssue():
+    # Xcode won't linkify this URL unless there is a non-whitespace character
+    # after it. Add a period on a new line to circumvent this. Also add a space
+    # before the period to make sure that Gitiles continues to correctly resolve
+    # the URL.
+    commit_desc.append_footer('Review-Url: %s .' % cl.GetIssueURL())
+  if options.contributor:
+    commit_desc.append_footer('Patch from %s.' % options.contributor)
+
+  print('Description:')
+  print(commit_desc.description)
+
+  branches = [merge_base, cl.GetBranchRef()]
+  if not options.force:
+    print_stats(options.similarity, options.find_copies, branches)
+
+  # We want to squash all this branch's commits into one commit with the proper
+  # description. We do this by doing a "reset --soft" to the base branch (which
+  # keeps the working copy the same), then landing that.
+  MERGE_BRANCH = 'git-cl-commit'
+  CHERRY_PICK_BRANCH = 'git-cl-cherry-pick'
+  # Delete the branches if they exist.
+  for branch in [MERGE_BRANCH, CHERRY_PICK_BRANCH]:
+    showref_cmd = ['show-ref', '--quiet', '--verify', 'refs/heads/%s' % branch]
+    result = RunGitWithCode(showref_cmd)
+    if result[0] == 0:
+      RunGit(['branch', '-D', branch])
+
+  # We might be in a directory that's present in this branch but not in the
+  # trunk.  Move up to the top of the tree so that git commands that expect a
+  # valid CWD won't fail after we check out the merge branch.
+  rel_base_path = settings.GetRelativeRoot()
+  if rel_base_path:
+    os.chdir(rel_base_path)
+
+  # Stuff our change into the merge branch.
+  # We wrap in a try...finally block so if anything goes wrong,
+  # we clean up the branches.
+  retcode = -1
+  pushed_to_pending = False
+  pending_ref = None
+  revision = None
+  try:
+    RunGit(['checkout', '-q', '-b', MERGE_BRANCH])
+    RunGit(['reset', '--soft', merge_base])
+    if options.contributor:
+      RunGit(
+          [
+            'commit', '--author', options.contributor,
+            '-m', commit_desc.description,
+          ])
+    else:
+      RunGit(['commit', '-m', commit_desc.description])
+
+    remote, branch = cl.FetchUpstreamTuple(cl.GetBranch())
+    mirror = settings.GetGitMirror(remote)
+    if mirror:
+      pushurl = mirror.url
+      git_numberer = _GitNumbererState.load(pushurl, branch)
+    else:
+      pushurl = remote  # Usually, this is 'origin'.
+      git_numberer = _GitNumbererState.load(
+          RunGit(['config', 'remote.%s.url' % remote]).strip(), branch)
+
+    if git_numberer.should_add_git_number:
+      # TODO(tandrii): run git fetch in a loop + autorebase when there there
+      # is no pending ref to push to?
+      logging.debug('Adding git number footers')
+      parent_msg = RunGit(['show', '-s', '--format=%B', merge_base]).strip()
+      commit_desc.update_with_git_number_footers(merge_base, parent_msg,
+                                                 branch)
+      # Ensure timestamps are monotonically increasing.
+      timestamp = max(1 + _get_committer_timestamp(merge_base),
+                      _get_committer_timestamp('HEAD'))
+      _git_amend_head(commit_desc.description, timestamp)
+      change_desc = ChangeDescription(commit_desc.description)
+      # If gnumbd is sitll ON and we ultimately push to branch with
+      # pending_prefix, gnumbd will modify footers we've just inserted with
+      # 'Original-', which is annoying but still technically correct.
+
+    pending_prefix = git_numberer.pending_prefix
+    if not pending_prefix or branch.startswith(pending_prefix):
+      # If not using refs/pending/heads/* at all, or target ref is already set
+      # to pending, then push to the target ref directly.
+      # NB(tandrii): I think branch.startswith(pending_prefix) never happens
+      # in practise. I really tried to create a new branch tracking
+      # refs/pending/heads/master directly and git cl land failed long before
+      # reaching this. Disagree? Comment on http://crbug.com/642493.
+      if pending_prefix:
+        print('\n\nYOU GOT A CHANCE TO WIN A FREE GIFT!\n\n'
+              'Grab your .git/config, add instructions how to reproduce '
+              'this, and post it to http://crbug.com/642493.\n'
+              'The first reporter gets a free "Black Swan" book from '
+              'tandrii@\n\n')
+      retcode, output = RunGitWithCode(
+          ['push', '--porcelain', pushurl, 'HEAD:%s' % branch])
+      pushed_to_pending = pending_prefix and branch.startswith(pending_prefix)
+    else:
+      # Cherry-pick the change on top of pending ref and then push it.
+      assert branch.startswith('refs/'), branch
+      assert pending_prefix[-1] == '/', pending_prefix
+      pending_ref = pending_prefix + branch[len('refs/'):]
+      retcode, output = PushToGitPending(pushurl, pending_ref)
+      pushed_to_pending = (retcode == 0)
+
+    if retcode == 0:
+      revision = RunGit(['rev-parse', 'HEAD']).strip()
+    logging.debug(output)
+  except:  # pylint: disable=bare-except
+    if _IS_BEING_TESTED:
+      logging.exception('this is likely your ACTUAL cause of test failure.\n'
+                        + '-' * 30 + '8<' + '-' * 30)
+      logging.error('\n' + '-' * 30 + '8<' + '-' * 30 + '\n\n\n')
+    raise
+  finally:
+    # And then swap back to the original branch and clean up.
+    RunGit(['checkout', '-q', cl.GetBranch()])
+    RunGit(['branch', '-D', MERGE_BRANCH])
+
+  if not revision:
+    print('Failed to push. If this persists, please file a bug.')
+    return 1
+
+  killed = False
+  if pushed_to_pending:
+    try:
+      revision = WaitForRealCommit(remote, revision, base_branch, branch)
+      # We set pushed_to_pending to False, since it made it all the way to the
+      # real ref.
+      pushed_to_pending = False
+    except KeyboardInterrupt:
+      killed = True
+
+  if cl.GetIssue():
+    to_pending = ' to pending queue' if pushed_to_pending else ''
+    viewvc_url = settings.GetViewVCUrl()
+    if not to_pending:
+      if viewvc_url and revision:
+        change_desc.append_footer(
+            'Committed: %s%s' % (viewvc_url, revision))
+      elif revision:
+        change_desc.append_footer('Committed: %s' % (revision,))
+    print('Closing issue '
+          '(you may be prompted for your codereview password)...')
+    cl.UpdateDescription(change_desc.description)
+    cl.CloseIssue()
+    props = cl.GetIssueProperties()
+    patch_num = len(props['patchsets'])
+    comment = "Committed patchset #%d (id:%d)%s manually as %s" % (
+        patch_num, props['patchsets'][-1], to_pending, revision)
+    if options.bypass_hooks:
+      comment += ' (tree was closed).' if GetTreeStatus() == 'closed' else '.'
+    else:
+      comment += ' (presubmit successful).'
+    cl.RpcServer().add_comment(cl.GetIssue(), comment)
+
+  if pushed_to_pending:
+    _, branch = cl.FetchUpstreamTuple(cl.GetBranch())
+    print('The commit is in the pending queue (%s).' % pending_ref)
+    print('It will show up on %s in ~1 min, once it gets a Cr-Commit-Position '
+          'footer.' % branch)
+
+  if os.path.isfile(POSTUPSTREAM_HOOK):
+    RunCommand([POSTUPSTREAM_HOOK, merge_base], error_ok=True)
+
+  return 1 if killed else 0
 
 
 @subcommand.usage('<patch url or issue id or issue url>')
@@ -5013,16 +4775,6 @@
                           options.directory)
 
 
-def CMDrebase(parser, args):
-  """Rebases current branch on top of svn repo."""
-  # Provide a wrapper for git svn rebase to help avoid accidental
-  # git svn dcommit.
-  # It's the only command that doesn't use parser at all since we just defer
-  # execution to git-svn.
-
-  return RunGitWithCode(['svn', 'rebase'] + args)[1]
-
-
 def GetTreeStatus(url=None):
   """Fetches the tree status and returns either 'open', 'closed',
   'unknown' or 'unset'."""