comparison mercurial/subrepo.py @ 13150:8617b8b74fae

subrepo: use low-level git-for-each-ref command in branchmap This command's output doesn't change across versions, and it also disambiguates cases where there are slashes in local branch names.
author Eric Eisner <ede@mit.edu>
date Tue, 14 Dec 2010 21:53:40 -0500
parents aae2d5cbde64
children 519ac79d680b
comparison
equal deleted inserted replaced
13149:735dd8e8a208 13150:8617b8b74fae
647 p.wait() 647 p.wait()
648 648
649 if p.returncode != 0 and p.returncode != 1: 649 if p.returncode != 0 and p.returncode != 1:
650 # there are certain error codes that are ok 650 # there are certain error codes that are ok
651 command = commands[0] 651 command = commands[0]
652 if command == 'cat-file': 652 if command in ('cat-file', 'symbolic-ref'):
653 return retdata, p.returncode 653 return retdata, p.returncode
654 # for all others, abort 654 # for all others, abort
655 raise util.Abort('git %s error %d in %s' % 655 raise util.Abort('git %s error %d in %s' %
656 (command, p.returncode, self._relpath)) 656 (command, p.returncode, self._relpath))
657 657
673 the current branch, 673 the current branch,
674 a map from git branch to revision 674 a map from git branch to revision
675 a map from revision to branches''' 675 a map from revision to branches'''
676 branch2rev = {} 676 branch2rev = {}
677 rev2branch = {} 677 rev2branch = {}
678 current = None 678 current, err = self._gitdir(['symbolic-ref', 'HEAD', '--quiet'])
679 out = self._gitcommand(['branch', '-a', '--no-color', 679 if err:
680 '--verbose', '--no-abbrev']) 680 current = None
681
682 out = self._gitcommand(['for-each-ref', '--format',
683 '%(objectname) %(refname)'])
681 for line in out.split('\n'): 684 for line in out.split('\n'):
682 if line[2:].startswith('(no branch)'): 685 revision, ref = line.split(' ')
686 if ref.startswith('refs/tags/'):
683 continue 687 continue
684 branch, revision = line[2:].split()[:2] 688 if ref.startswith('refs/remotes/') and ref.endswith('/HEAD'):
685 if revision == '->' or branch.endswith('/HEAD'):
686 continue # ignore remote/HEAD redirects 689 continue # ignore remote/HEAD redirects
687 if '/' in branch and not branch.startswith('remotes/'): 690 branch2rev[ref] = revision
688 # old git compatability 691 rev2branch.setdefault(revision, []).append(ref)
689 branch = 'remotes/' + branch
690 if line[0] == '*':
691 current = branch
692 branch2rev[branch] = revision
693 rev2branch.setdefault(revision, []).append(branch)
694 return current, branch2rev, rev2branch 692 return current, branch2rev, rev2branch
695 693
696 def _gittracking(self, branches): 694 def _gittracking(self, branches):
697 'return map of remote branch to local tracking branch' 695 'return map of remote branch to local tracking branch'
698 # assumes no more than one local tracking branch for each remote 696 # assumes no more than one local tracking branch for each remote
699 tracking = {} 697 tracking = {}
700 for b in branches: 698 for b in branches:
701 if b.startswith('remotes/'): 699 if b.startswith('refs/remotes/'):
702 continue 700 continue
703 remote = self._gitcommand(['config', 'branch.%s.remote' % b]) 701 remote = self._gitcommand(['config', 'branch.%s.remote' % b])
704 if remote: 702 if remote:
705 ref = self._gitcommand(['config', 'branch.%s.merge' % b]) 703 ref = self._gitcommand(['config', 'branch.%s.merge' % b])
706 tracking['remotes/%s/%s' % (remote, ref.split('/')[-1])] = b 704 tracking['refs/remotes/%s/%s' %
705 (remote, ref.split('/', 2)[2])] = b
707 return tracking 706 return tracking
708 707
709 def _fetch(self, source, revision): 708 def _fetch(self, source, revision):
710 if not os.path.exists('%s/.git' % self._path): 709 if not os.path.exists('%s/.git' % self._path):
711 self._ui.status(_('cloning subrepo %s\n') % self._relpath) 710 self._ui.status(_('cloning subrepo %s\n') % self._relpath)
756 rawcheckout() 755 rawcheckout()
757 return 756 return
758 branches = rev2branch[revision] 757 branches = rev2branch[revision]
759 firstlocalbranch = None 758 firstlocalbranch = None
760 for b in branches: 759 for b in branches:
761 if b == 'master': 760 if b == 'refs/heads/master':
762 # master trumps all other branches 761 # master trumps all other branches
763 self._gitcommand(['checkout', 'master']) 762 self._gitcommand(['checkout', 'refs/heads/master'])
764 return 763 return
765 if not firstlocalbranch and not b.startswith('remotes/'): 764 if not firstlocalbranch and not b.startswith('refs/remotes/'):
766 firstlocalbranch = b 765 firstlocalbranch = b
767 if firstlocalbranch: 766 if firstlocalbranch:
768 self._gitcommand(['checkout', firstlocalbranch]) 767 self._gitcommand(['checkout', firstlocalbranch])
769 return 768 return
770 769
777 remote = b 776 remote = b
778 break 777 break
779 778
780 if remote not in tracking: 779 if remote not in tracking:
781 # create a new local tracking branch 780 # create a new local tracking branch
782 local = remote.split('/')[-1] 781 local = remote.split('/', 2)[2]
783 self._gitcommand(['checkout', '-b', local, remote]) 782 self._gitcommand(['checkout', '-b', local, remote])
784 elif self._gitisancestor(branch2rev[tracking[remote]], remote): 783 elif self._gitisancestor(branch2rev[tracking[remote]], remote):
785 # When updating to a tracked remote branch, 784 # When updating to a tracked remote branch,
786 # if the local tracking branch is downstream of it, 785 # if the local tracking branch is downstream of it,
787 # a normal `git pull` would have performed a "fast-forward merge" 786 # a normal `git pull` would have performed a "fast-forward merge"
822 def push(self, force): 821 def push(self, force):
823 # if a branch in origin contains the revision, nothing to do 822 # if a branch in origin contains the revision, nothing to do
824 current, branch2rev, rev2branch = self._gitbranchmap() 823 current, branch2rev, rev2branch = self._gitbranchmap()
825 if self._state[1] in rev2branch: 824 if self._state[1] in rev2branch:
826 for b in rev2branch[self._state[1]]: 825 for b in rev2branch[self._state[1]]:
827 if b.startswith('remotes/origin/'): 826 if b.startswith('refs/remotes/origin/'):
828 return True 827 return True
829 for b, revision in branch2rev.iteritems(): 828 for b, revision in branch2rev.iteritems():
830 if b.startswith('remotes/origin/'): 829 if b.startswith('refs/remotes/origin/'):
831 if self._gitisancestor(self._state[1], revision): 830 if self._gitisancestor(self._state[1], revision):
832 return True 831 return True
833 # otherwise, try to push the currently checked out branch 832 # otherwise, try to push the currently checked out branch
834 cmd = ['push'] 833 cmd = ['push']
835 if force: 834 if force:
839 if not self._gitisancestor(self._state[1], current): 838 if not self._gitisancestor(self._state[1], current):
840 self._ui.warn(_('unrelated git branch checked out ' 839 self._ui.warn(_('unrelated git branch checked out '
841 'in subrepo %s\n') % self._relpath) 840 'in subrepo %s\n') % self._relpath)
842 return False 841 return False
843 self._ui.status(_('pushing branch %s of subrepo %s\n') % 842 self._ui.status(_('pushing branch %s of subrepo %s\n') %
844 (current, self._relpath)) 843 (current.split('/', 2)[2], self._relpath))
845 self._gitcommand(cmd + ['origin', current]) 844 self._gitcommand(cmd + ['origin', current])
846 return True 845 return True
847 else: 846 else:
848 self._ui.warn(_('no branch checked out in subrepo %s\n' 847 self._ui.warn(_('no branch checked out in subrepo %s\n'
849 'cannot push revision %s') % 848 'cannot push revision %s') %