Commits

Ian Bicking committed 83e6a98 Merge

merge

Comments (0)

Files changed (4)

 * Fixed handling read-only attributes of build files, e.g. of Subversion and
   Bazaar on Windows.
 
+<<<<<<< /home/ianb/src/pip/docs/news.txt
 * When downloading a file from a redirect, use the redirected
   location's extension to guess the compression (happens specifically
   when redirecting to a bitbucket.org tip.gz file).
 
+* Editable freeze URLs now always use revision hash/id rather than tip or
+  branch names which could move.
+
+* Fixed comparison of repo URLs so incidental differences such as
+  presence/absence of final slashes or quoted/unquoted special
+  characters don't trigger "ignore/switch/wipe/backup" choice.
+
+* Fixed handling of attempt to checkout editable install to a
+  non-empty, non-repo directory.
+
 0.4
 ---
 
             req = '-e %s' % req
         return '\n'.join(list(self.comments)+[str(req)])+'\n'
 
+(_CAN_SWITCH, _NO_SWITCH) = (1, 2)
+    
 class VersionControl(object):
     name = ''
     dirname = ''
         assert not location.rstrip('/').endswith(self.dirname), 'Bad directory: %s' % location
         return self.get_url(location), self.get_revision(location)
 
+    def normalize_url(self, url):
+        """
+        Normalize a URL for comparison by unquoting it and removing any trailing slash.
+        """
+        return urllib.unquote(url).rstrip('/')
+
+    def compare_urls(self, url1, url2):
+        """
+        Compare two repo URLs for identity, ignoring incidental differences.
+        """
+        return (self.normalize_url(url1) == self.normalize_url(url2))
+    
     def parse_vcs_bundle_file(self, content):
         """
         Takes the contents of the bundled text file that explains how to revert
         """
         raise NotImplementedError
 
+    def switch(self, dest, url, rev_options):
+        """
+        Switch the repo at ``dest`` to point to ``URL``.
+        """
+        raise NotImplemented
+
+    def update(self, dest, rev_options):
+        """
+        Update an already-existing repo to the given ``rev_options``.
+        """
+        raise NotImplementedError
+    
+    def check_destination(self, dest, url, rev_options, rev_display):
+        """
+        Prepare a location to receive a checkout/clone.
+
+        Return True if the location is ready for (and requires) a
+        checkout/clone, False otherwise.
+        """
+        checkout = True
+        prompt = False
+        if os.path.exists(dest):
+            checkout = False
+            if os.path.exists(os.path.join(dest, self.dirname)):
+                existing_url = self.get_url(dest)
+                if self.compare_urls(existing_url, url):
+                    logger.info('%s in %s exists, and has correct URL (%s)'
+                                % (self.repo_name.title(), display_path(dest), url))
+                    logger.notify('Updating %s %s%s'
+                                  % (display_path(dest), self.repo_name, rev_display))
+                    self.update(dest, rev_options)
+                else:
+                    logger.warn('%s %s in %s exists with URL %s'
+                                % (self.name, self.repo_name, display_path(dest), existing_url))
+                    prompt = ('(s)witch, (i)gnore, (w)ipe, (b)ackup ', ('s', 'i', 'w', 'b'))
+            else:
+                logger.warn('Directory %s already exists, and is not a %s %s.'
+                            % (dest, self.name, self.repo_name))
+                prompt = ('(i)gnore, (w)ipe, (b)ackup ', ('i', 'w', 'b'))
+        if prompt:
+            logger.warn('The plan is to install the %s repository %s'
+                        % (self.name, url))
+            response = ask('What to do?  %s' % prompt[0], prompt[1])
+
+            if response == 's':
+                logger.notify('Switching %s %s to %s%s'
+                              % (self.repo_name, display_path(dest), url, rev_display))
+                self.switch(dest, url, rev_options)
+            elif response == 'i':
+                # do nothing
+                pass
+            elif response == 'w':
+                logger.warn('Deleting %s' % display_path(dest))
+                shutil.rmtree(dest)
+                checkout = True
+            elif response == 'b':
+                dest_dir = backup_dir(dest)
+                logger.warn('Backing up %s to %s'
+                            % (display_path(dest), dest_dir))
+                shutil.move(dest, dest_dir)
+                checkout = True
+        return checkout
+    
     def unpack(self, location):
         raise NotImplementedError
 
 class Subversion(VersionControl):
     name = 'svn'
     dirname = '.svn'
+    repo_name = 'checkout'
     schemes = ('svn', 'svn+ssh', 'svn+http', 'svn+https')
     bundle_file = 'svn-checkout.txt'
     guide = ('# This was an svn checkout; to make it a checkout again run:\n'
         if not match:
             logger.warn('Cannot determine URL of svn checkout %s' % display_path(location))
             logger.info('Output that cannot be parsed: \n%s' % output)
-            return 'unknown', 'unknown'
+            return None, None
         url = match.group(1).strip()
         match = _svn_revision_re.search(output)
         if not match:
             logger.warn('Cannot determine revision of svn checkout %s' % display_path(location))
             logger.info('Output that cannot be parsed: \n%s' % output)
-            return url, 'unknown'
+            return url, None
         return url, match.group(1)
 
+    def get_url(self, location):
+        return self.get_info(location)[0]
+
+    def get_revision(self, location):
+        return self.get_info(location)[1]
+    
     def parse_vcs_bundle_file(self, content):
         for line in content.splitlines():
             if not line.strip() or line.strip().startswith('#'):
         finally:
             logger.indent -= 2
 
+    def switch(self, dest, url, rev_options):
+        call_subprocess(
+            ['svn', 'switch'] + rev_options + [url, dest])
+            
+    def update(self, dest, rev_options):
+        call_subprocess(
+            ['svn', 'update'] + rev_options + [dest])
+
     def obtain(self, dest):
         url, rev = self.get_url_rev()
         if rev:
         else:
             rev_options = []
             rev_display = ''
-        checkout = True
-        if os.path.exists(os.path.join(dest, self.dirname)):
-            existing_url = self.get_info(dest)[0]
-            checkout = False
-            if existing_url == url:
-                logger.info('Checkout in %s exists, and has correct URL (%s)'
-                            % (display_path(dest), url))
-                logger.notify('Updating checkout %s%s'
-                              % (display_path(dest), rev_display))
-                call_subprocess(
-                    ['svn', 'update'] + rev_options + [dest])
-            else:
-                logger.warn('svn checkout in %s exists with URL %s'
-                            % (display_path(dest), existing_url))
-                logger.warn('The plan is to install the svn repository %s'
-                            % url)
-                response = ask('What to do?  (s)witch, (i)gnore, (w)ipe, (b)ackup ', ('s', 'i', 'w', 'b'))
-                if response == 's':
-                    logger.notify('Switching checkout %s to %s%s'
-                                  % (display_path(dest), url, rev_display))
-                    call_subprocess(
-                        ['svn', 'switch'] + rev_options + [url, dest])
-                elif response == 'i':
-                    # do nothing
-                    pass
-                elif response == 'w':
-                    logger.warn('Deleting %s' % display_path(dest))
-                    shutil.rmtree(dest)
-                    checkout = True
-                elif response == 'b':
-                    dest_dir = backup_dir(dest)
-                    logger.warn('Backing up %s to %s'
-                                % (display_path(dest), dest_dir))
-                    shutil.move(dest, dest_dir)
-                    checkout = True
-        if checkout:
+        if self.check_destination(dest, url, rev_options, rev_display):
             logger.notify('Checking out %s%s to %s'
                           % (url, rev_display, display_path(dest)))
             call_subprocess(
         parts = repo.split('/')
         ## FIXME: why not project name?
         egg_project_name = dist.egg_name().split('-', 1)[0]
+        rev = self.get_revision(location)
         if parts[-2] in ('tags', 'tag'):
             # It's a tag, perfect!
-            return 'svn+%s#egg=%s-%s' % (repo, egg_project_name, parts[-1])
+            full_egg_name = '%s-%s' % (egg_project_name, parts[-1])
         elif parts[-2] in ('branches', 'branch'):
             # It's a branch :(
-            rev = self.get_revision(location)
-            return 'svn+%s@%s#egg=%s%s-r%s' % (repo, rev, dist.egg_name(), parts[-1], rev)
+            full_egg_name = '%s-%s-r%s' % (dist.egg_name(), parts[-1], rev)
         elif parts[-1] == 'trunk':
             # Trunk :-/
-            rev = self.get_revision(location)
+            full_egg_name = '%s-dev_r%s' % (dist.egg_name(), rev)
             if find_tags:
                 tag_url = '/'.join(parts[:-1]) + '/tags'
                 tag_revs = self.get_tag_revs(tag_url)
                 match = self.find_tag_match(rev, tag_revs)
                 if match:
                     logger.notify('trunk checkout %s seems to be equivalent to tag %s' % match)
-                    return 'svn+%s/%s#egg=%s-%s' % (tag_url, match, egg_project_name, match)
-            return 'svn+%s@%s#egg=%s-dev' % (repo, rev, dist.egg_name())
+                    repo = '%s/%s' % (tag_url, match)
+                    full_egg_name = '%s-%s' % (egg_project_name, match)
         else:
             # Don't know what it is
             logger.warn('svn URL does not fit normal structure (tags/branches/trunk): %s' % repo)
-            rev = self.get_revision(location)
-            return 'svn+%s@%s#egg=%s-dev' % (repo, rev, egg_project_name)
+            full_egg_name = '%s-dev_r%s' % (egg_project_name, rev)
+        return 'svn+%s@%s#egg=%s' % (repo, rev, full_egg_name)
 
 vcs.register(Subversion)
 
 class Git(VersionControl):
     name = 'git'
     dirname = '.git'
+    repo_name = 'clone'
     schemes = ('git', 'git+http', 'git+ssh', 'git+git')
     bundle_file = 'git-clone.txt'
     guide = ('# This was a Git repo; to make it a repo again run:\n'
                                         % (rev, display_path(dest)))
         return [rev]
 
+    def switch(self, dest, url, rev_options):
+        remote_name = self.get_remote_name(location)
+        call_subprocess(
+            [GIT_CMD, 'config', 'remote.%s.url' % remote_name, url], cwd=dest)
+        call_subprocess(
+            [GIT_CMD, 'checkout', '-q'] + rev_options, cwd=dest)
+
+    def update(self, dest, rev_options):
+        call_subprocess([GIT_CMD, 'fetch', '-q'], cwd=dest)
+        call_subprocess(
+            [GIT_CMD, 'checkout', '-q', '-f'] + rev_options, cwd=dest)
+
     def obtain(self, dest):
         url, rev = self.get_url_rev()
         if rev:
         else:
             rev_options = ['origin/master']
             rev_display = ''
-        clone = True
-        if os.path.exists(os.path.join(dest, self.dirname)):
-            existing_url = self.get_url(dest)
-            rev_options = self.check_rev_options(rev, dest, rev_options)
-            clone = False
-            if existing_url == url:
-                logger.info('Clone in %s exists, and has correct URL (%s)'
-                            % (display_path(dest), url))
-                logger.notify('Updating clone %s%s'
-                              % (display_path(dest), rev_display))
-                call_subprocess([GIT_CMD, 'fetch', '-q'], cwd=dest)
-                call_subprocess(
-                    [GIT_CMD, 'checkout', '-q', '-f'] + rev_options, cwd=dest)
-            else:
-                logger.warn('Git clone in %s exists with URL %s'
-                            % (display_path(dest), existing_url))
-                logger.warn('The plan is to install the Git repository %s'
-                            % url)
-                response = ask('What to do?  (s)witch, (i)gnore, (w)ipe, (b)ackup ', ('s', 'i', 'w', 'b'))
-                if response == 's':
-                    logger.notify('Switching clone %s to %s%s'
-                                  % (display_path(dest), url, rev_display))
-                    call_subprocess(
-                        [GIT_CMD, 'config', 'remote.origin.url', url], cwd=dest)
-                    call_subprocess(
-                        [GIT_CMD, 'checkout', '-q'] + rev_options, cwd=dest)
-                elif response == 'i':
-                    # do nothing
-                    pass
-                elif response == 'w':
-                    logger.warn('Deleting %s' % display_path(dest))
-                    shutil.rmtree(dest)
-                    clone = True
-                elif response == 'b':
-                    dest_dir = backup_dir(dest)
-                    logger.warn('Backing up %s to %s' % (display_path(dest), dest_dir))
-                    shutil.move(dest, dest_dir)
-                    clone = True
-        if clone:
+        if self.check_destination(dest, url, rev_options, rev_display):
             logger.notify('Cloning %s%s to %s' % (url, rev_display, display_path(dest)))
             call_subprocess(
                 [GIT_CMD, 'clone', '-q', url, dest])
             call_subprocess(
                 [GIT_CMD, 'checkout', '-q'] + rev_options, cwd=dest)
 
+    def get_remote_name(self, location):
+        """First gets the name of the current HEAD, e.g. master. Then returns
+        the name of the remote which the head is tracking."""
+        head_ref = call_subprocess(
+            [GIT_CMD, 'symbolic-ref', '-q', 'HEAD'],
+            show_stdout=False, cwd=location).strip()
+        head_ref = head_ref.split('refs/heads/', 1)[-1]
+        remote_name = call_subprocess(
+            [GIT_CMD, 'config', 'branch.%s.remote' % head_ref],
+            show_stdout=False, cwd=location).strip()
+        return remote_name
+
     def get_url(self, location):
+        remote_name = self.get_remote_name(location)
         url = call_subprocess(
-            [GIT_CMD, 'config', 'remote.origin.url'],
+            [GIT_CMD, 'config', 'remote.%s.url' % remote_name],
             show_stdout=False, cwd=location)
         return url.strip()
 
             [GIT_CMD, 'rev-parse', 'HEAD'], show_stdout=False, cwd=location)
         return current_rev.strip()
 
-    def get_master_revision(self, location):
-        master_rev = call_subprocess(
-            [GIT_CMD, 'rev-parse', 'master'], show_stdout=False, cwd=location)
-        return master_rev.strip()
-
     def get_tag_revs(self, location):
         tags = call_subprocess(
             [GIT_CMD, 'tag'], show_stdout=False, cwd=location)
             return None
         current_rev = self.get_revision(location)
         tag_revs = self.get_tag_revs(location)
-        master_rev = self.get_master_revision(location)
         branch_revs = self.get_branch_revs(location)
+        remote_name = self.get_remote_name(location)
 
         if current_rev in tag_revs:
-            # It's a tag, perfect!
-            tag = tag_revs.get(current_rev, current_rev)
-            return '%s@%s#egg=%s-%s' % (repo, tag, egg_project_name, tag)
-        elif current_rev in branch_revs:
-            # It's the head of a branch, nice too.
-            branch = branch_revs.get(current_rev, current_rev)
-            return '%s@%s#egg=%s-%s' % (repo, current_rev, dist.egg_name(), current_rev)
-        elif current_rev == master_rev:
-            if find_tags:
-                if current_rev in tag_revs:
-                    tag = tag_revs.get(current_rev, current_rev)
-                    logger.notify('Revision %s seems to be equivalent to tag %s' % (current_rev, tag))
-                    return '%s@%s#egg=%s-%s' % (repo, tag, egg_project_name, tag)
-            return '%s@%s#egg=%s-dev' % (repo, master_rev, dist.egg_name())
+            # It's a tag
+            full_egg_name = '%s-%s' % (egg_project_name, tag_revs[current_rev])
+        elif (current_rev in branch_revs and
+              branch_revs[current_rev] != '%s/master' % remote_name):
+            # It's the head of a branch
+            full_egg_name = '%s-%s' % (dist.egg_name(),
+                                       branch_revs[current_rev].replace('%s/' % remote_name, ''))
         else:
-            # Don't know what it is
-            logger.warn('Git URL does not fit normal structure: %s' % repo)
-            return '%s@%s#egg=%s-dev' % (repo, current_rev, egg_project_name)
+            full_egg_name = '%s-dev' % dist.egg_name()
+            
+        return '%s@%s#egg=%s' % (repo, current_rev, full_egg_name)
 
     def get_url_rev(self):
         """
 class Mercurial(VersionControl):
     name = 'hg'
     dirname = '.hg'
+    repo_name = 'clone'
     schemes = ('hg', 'hg+http', 'hg+ssh')
     bundle_file = 'hg-clone.txt'
     guide = ('# This was a Mercurial repo; to make it a repo again run:\n'
         finally:
             shutil.rmtree(temp_dir)
 
+    def switch(self, dest, url, rev_options):
+        repo_config = os.path.join(dest, self.dirname, 'hgrc')
+        config = ConfigParser.SafeConfigParser()
+        try:
+            config.read(repo_config)
+            config.set('paths', 'default', url)
+            config_file = open(repo_config, 'w')
+            config.write(config_file)
+            config_file.close()
+        except (OSError, ConfigParser.NoSectionError), e:
+            logger.warn(
+                'Could not switch Mercurial repository to %s: %s'
+                % (url, e))
+        else:
+            call_subprocess(['hg', 'update', '-q'] + rev_options, cwd=dest)
+
+    def update(self, dest, rev_options):
+        call_subprocess(['hg', 'pull', '-q'], cwd=dest)
+        call_subprocess(
+            ['hg', 'update', '-q'] + rev_options, cwd=dest)
+        
     def obtain(self, dest):
         url, rev = self.get_url_rev()
         if rev:
         else:
             rev_options = []
             rev_display = ''
-        clone = True
-        if os.path.exists(os.path.join(dest, '.hg')):
-            existing_url = self.get_url(dest)
-            clone = False
-            if existing_url == url:
-                logger.info('Clone in %s exists, and has correct URL (%s)'
-                            % (display_path(dest), url))
-                logger.notify('Updating clone %s%s'
-                              % (display_path(dest), rev_display))
-                call_subprocess(['hg', 'pull', '-q'], cwd=dest)
-                call_subprocess(
-                    ['hg', 'update', '-q'] + rev_options, cwd=dest)
-            else:
-                logger.warn('Mercurial clone in %s exists with URL %s'
-                            % (display_path(dest), existing_url))
-                logger.warn('The plan is to install the Mercurial repository %s'
-                            % url)
-                response = ask('What to do?  (s)witch, (i)gnore, (w)ipe, (b)ackup ', ('s', 'i', 'w', 'b'))
-                if response == 's':
-                    logger.notify('Switching clone %s to %s%s'
-                                  % (display_path(dest), url, rev_display))
-                    repo_config = os.path.join(dest, '.hg/hgrc')
-                    config = ConfigParser.SafeConfigParser()
-                    try:
-                        config_file = open(repo_config, 'wb')
-                        config.readfp(config_file)
-                        config.set('paths', ''.join(rev_options), url)
-                        config.write(config_file)
-                    except (OSError, ConfigParser.NoSectionError):
-                        logger.warn(
-                            'Could not switch Mercurial repository to %s: %s'
-                                % (url, e))
-                    else:
-                        call_subprocess(
-                            ['hg', 'update', '-q'] + rev_options, cwd=dest)
-                elif response == 'i':
-                    # do nothing
-                    pass
-                elif response == 'w':
-                    logger.warn('Deleting %s' % display_path(dest))
-                    shutil.rmtree(dest)
-                    clone = True
-                elif response == 'b':
-                    dest_dir = backup_dir(dest)
-                    logger.warn('Backing up %s to %s' % (display_path(dest), dest_dir))
-                    shutil.move(dest, dest_dir)
-                    clone = True
-        if clone:
+        if self.check_destination(dest, url, rev_options, rev_display):
             logger.notify('Cloning hg %s%s to %s'
                           % (url, rev_display, display_path(dest)))
             call_subprocess(['hg', 'clone', '-q', url, dest])
             url = filename_to_url(url)
         return url.strip()
 
-    def get_tip_revision(self, location):
-        current_rev = call_subprocess(
-            ['hg', 'tip', '--template={rev}'], show_stdout=False, cwd=location)
-        return current_rev.strip()
-
     def get_tag_revs(self, location):
         tags = call_subprocess(
             ['hg', 'tags'], show_stdout=False, cwd=location)
             show_stdout=False, cwd=location).strip()
         return current_revision
 
+    def get_revision_hash(self, location):
+        current_rev_hash = call_subprocess(
+            ['hg', 'parents', '--template={node}'],
+            show_stdout=False, cwd=location).strip()
+        return current_rev_hash
+
     def get_src_requirement(self, dist, location, find_tags):
         repo = self.get_url(location)
         if not repo.lower().startswith('hg:'):
         if not repo:
             return None
         current_rev = self.get_revision(location)
+        current_rev_hash = self.get_revision_hash(location)
         tag_revs = self.get_tag_revs(location)
         branch_revs = self.get_branch_revs(location)
-        tip_rev = self.get_tip_revision(location)
         if current_rev in tag_revs:
-            # It's a tag, perfect!
-            tag = tag_revs.get(current_rev, current_rev)
-            return '%s@%s#egg=%s-%s' % (repo, tag, egg_project_name, tag)
+            # It's a tag
+            full_egg_name = '%s-%s' % (egg_project_name, tag_revs[current_rev])
         elif current_rev in branch_revs:
-            # It's the tip of a branch, nice too.
-            branch = branch_revs.get(current_rev, current_rev)
-            return '%s@%s#egg=%s-%s' % (repo, branch, dist.egg_name(), current_rev)
-        elif current_rev == tip_rev:
-            if find_tags:
-                if current_rev in tag_revs:
-                    tag = tag_revs.get(current_rev, current_rev)
-                    logger.notify('Revision %s seems to be equivalent to tag %s' % (current_rev, tag))
-                    return '%s@%s#egg=%s-%s' % (repo, tag, egg_project_name, tag)
-            return '%s@%s#egg=%s-dev' % (repo, tip_rev, dist.egg_name())
+            # It's the tip of a branch
+            full_egg_name = '%s-%s' % (dist.egg_name(), branch_revs[current_rev])
         else:
-            # Don't know what it is
-            logger.warn('Mercurial URL does not fit normal structure: %s' % repo)
-            return '%s@%s#egg=%s-dev' % (repo, current_rev, egg_project_name)
+            full_egg_name = '%s-dev' % dist.egg_name()
+        return '%s@%s#egg=%s' % (repo, current_rev_hash, full_egg_name)
 
 vcs.register(Mercurial)
 
 class Bazaar(VersionControl):
     name = 'bzr'
     dirname = '.bzr'
+    repo_name = 'branch'
     bundle_file = 'bzr-branch.txt'
     schemes = ('bzr', 'bzr+http', 'bzr+https', 'bzr+ssh', 'bzr+sftp')
     guide = ('# This was a Bazaar branch; to make it a branch again run:\n'
         finally:
             shutil.rmtree(temp_dir)
 
+    def switch(self, dest, url, rev_options):
+        call_subprocess([BZR_CMD, 'switch', url], cwd=dest)
+
+    def update(self, dest, rev_options):
+        call_subprocess(
+            [BZR_CMD, 'pull', '-q'] + rev_options, cwd=dest)
+            
     def obtain(self, dest):
         url, rev = self.get_url_rev()
         if rev:
         else:
             rev_options = []
             rev_display = ''
-        branch = True
-        update = False
-        if os.path.exists(os.path.join(dest, '.bzr')):
-            existing_url = self.get_url(dest)
-            branch = False
-            if existing_url == url:
-                logger.info('Checkout in %s exists, and has correct URL (%s)'
-                            % (display_path(dest), url))
-                logger.notify('Updating branch %s%s'
-                              % (display_path(dest), rev_display))
-                branch = update = True
-            else:
-                logger.warn('Bazaar branch in %s exists with URL %s'
-                            % (display_path(dest), existing_url))
-                logger.warn('The plan is to install the Bazaar repository %s'
-                            % url)
-                response = ask('What to do?  (s)witch, (i)gnore, (w)ipe, (b)ackup ', ('s', 'i', 'w', 'b'))
-                if response == 's':
-                    logger.notify('Switching branch %s to %s%s'
-                                  % (display_path(dest), url, rev_display))
-                    call_subprocess([BZR_CMD, 'switch', url], cwd=dest)
-                elif response == 'i':
-                    # do nothing
-                    pass
-                elif response == 'w':
-                    logger.warn('Deleting %s' % display_path(dest))
-                    shutil.rmtree(dest)
-                    branch = True
-                elif response == 'b':
-                    dest_dir = backup_dir(dest)
-                    logger.warn('Backing up %s to %s' % (display_path(dest), dest_dir))
-                    shutil.move(dest, dest_dir)
-                    branch = True
-        if branch:
+        if self.check_destination(dest, url, rev_options, rev_display):
             logger.notify('Checking out %s%s to %s'
                           % (url, rev_display, display_path(dest)))
-            if update:
-                call_subprocess(
-                    [BZR_CMD, 'pull', '-q'] + rev_options + [url], cwd=dest)
-            else:
-                call_subprocess(
-                    [BZR_CMD, 'branch', '-q'] + rev_options + [url, dest])
+            call_subprocess(
+                [BZR_CMD, 'branch', '-q'] + rev_options + [url, dest])
 
     def get_url_rev(self):
         # hotfix the URL scheme after removing bzr+ from bzr+ssh:// readd it
             [BZR_CMD, 'revno'], show_stdout=False, cwd=location)
         return revision.splitlines()[-1]
 
-    def get_newest_revision(self, location):
-        url = self.get_url(location)
-        revision = call_subprocess(
-            [BZR_CMD, 'revno', url], show_stdout=False, cwd=location)
-        return revision.splitlines()[-1]
-
     def get_tag_revs(self, location):
         tags = call_subprocess(
             [BZR_CMD, 'tags'], show_stdout=False, cwd=location)
             return None
         current_rev = self.get_revision(location)
         tag_revs = self.get_tag_revs(location)
-        newest_rev = self.get_newest_revision(location)
+
         if current_rev in tag_revs:
-            # It's a tag, perfect!
+            # It's a tag
             tag = tag_revs.get(current_rev, current_rev)
-            return '%s@%s#egg=%s-%s' % (repo, tag, egg_project_name, tag)
-        elif current_rev == newest_rev:
-            if find_tags:
-                if current_rev in tag_revs:
-                    tag = tag_revs.get(current_rev, current_rev)
-                    logger.notify('Revision %s seems to be equivalent to tag %s' % (current_rev, tag))
-                    return '%s@%s#egg=%s-%s' % (repo, tag, egg_project_name, tag)
-            return '%s@%s#egg=%s-dev' % (repo, newest_rev, dist.egg_name())
+            full_egg_name = '%s-%s' % (egg_project_name, tag_revs[current_rev])
         else:
-            # Don't know what it is
-            logger.warn('Bazaar URL does not fit normal structure: %s' % repo)
-            return '%s@%s#egg=%s-dev' % (repo, current_rev, egg_project_name)
+            full_egg_name = '%s-dev_r%s' % (dist.egg_name(), current_rev)
+        return '%s@%s#egg=%s' % (repo, current_rev, full_egg_name)
 
 vcs.register(Bazaar)
 

tests/test_basic.txt

     >>> assert 'src/django-registration' in result.files_created
     >>> assert 'src/django-registration/.hg' in result.files_created
 
+Presence or absence of final slash is also normalized::
+
+    >>> result = run_pip('install', '-e', 'hg+http://bitbucket.org/ubernostrum/django-registration#egg=django-registration', expect_error=True)
+    >>> assert 'pip-log.txt' not in result.files_created, result.files_created['pip-log.txt'].bytes
+
 Checking out from Bazaar::
 
     >>> reset_env()
-    >>> result = run_pip('install', '-e', 'bzr+http://bazaar.launchpad.net/%7Ejezdez/pip-test/test/#egg=pip-test', expect_error=True)
+    >>> result = run_pip('install', '-e', 'bzr+http://bazaar.launchpad.net/%7Ejezdez/pip-test/test/@1#egg=pip-test', expect_error=True)
     >>> egg_link = result.files_created[lib_py + 'site-packages/pip-test.egg-link']
     >>> # FIXME: I don't understand why there's a trailing . here:
     >>> egg_link.bytes
     >>> assert (lib_py + 'site-packages/easy-install.pth') in result.files_updated
     >>> assert 'src/pip-test' in result.files_created
     >>> assert 'src/pip-test/.bzr' in result.files_created
+
+Urlquoted characters are normalized for repo URL comparison::
+
+    >>> result = run_pip('install', '-e', 'bzr+http://bazaar.launchpad.net/~jezdez/pip-test/test#egg=pip-test', expect_error=True)
+    >>> assert 'pip-log.txt' not in result.files_created, result.files_created['pip-log.txt'].bytes
+

tests/test_freeze.txt

     >>> print result
     Script result: ...ython... ../../pip.py -E .../test-scratch freeze
     -- stdout: --------------------
-    -e svn+http://svn.colorstudy.com/INITools/trunk@3472#egg=INITools-0.2.1dev_r3472-py2...-dev
+    -e svn+http://svn.colorstudy.com/INITools/trunk@3472#egg=INITools-0.2.1dev_r3472-py2...-dev_r3472
     simplejson==1.7.4...
     <BLANKLINE>
 
     >>> result = run_pip('freeze', expect_stderr=True)
     >>> print result
     Script result: ...ython... ../../pip.py -E .../test-scratch freeze
-    -- stderr: --------------------
-    Git URL does not fit normal structure: git://github.com/jezdez/django-pagination.git
-    <BLANKLINE>
     -- stdout: --------------------
     -e git://github.com/jezdez/django-pagination.git@...#egg=django_pagination-...
     ...
     <BLANKLINE>
 
-    >>> result = run_pip('freeze', '-f', 'git://github.com/jezdez/django-pagination.git#egg=django_pagination-dev', expect_stderr=True)
+    >>> result = run_pip('freeze', '-f', 'git://github.com/jezdez/django-pagination.git#egg=django_pagination', expect_stderr=True)
     >>> print result
-    Script result: ...ython... ../../pip.py -E .../test-scratch freeze -f git://github.com/jezdez/django-pagination.git#egg=django_pagination-dev
-    -- stderr: --------------------
-    Git URL does not fit normal structure: git://github.com/jezdez/django-pagination.git
-    <BLANKLINE>
+    Script result: ...ython... ../../pip.py -E .../test-scratch freeze -f git://github.com/jezdez/django-pagination.git#egg=django_pagination
     -- stdout: --------------------
-    -f git://github.com/jezdez/django-pagination.git#egg=django_pagination-dev
-    -e git://github.com/jezdez/django-pagination.git@...#egg=django_pagination-dev
+    -f git://github.com/jezdez/django-pagination.git#egg=django_pagination
+    -e git://github.com/jezdez/django-pagination.git@...#egg=django_pagination-...-dev
     ...
     <BLANKLINE>