658 return retdata, p.returncode |
658 return retdata, p.returncode |
659 |
659 |
660 def _gitstate(self): |
660 def _gitstate(self): |
661 return self._gitcommand(['rev-parse', 'HEAD']) |
661 return self._gitcommand(['rev-parse', 'HEAD']) |
662 |
662 |
|
663 def _gitcurrentbranch(self): |
|
664 current, err = self._gitdir(['symbolic-ref', 'HEAD', '--quiet']) |
|
665 if err: |
|
666 current = None |
|
667 return current |
|
668 |
663 def _githavelocally(self, revision): |
669 def _githavelocally(self, revision): |
664 out, code = self._gitdir(['cat-file', '-e', revision]) |
670 out, code = self._gitdir(['cat-file', '-e', revision]) |
665 return code == 0 |
671 return code == 0 |
666 |
672 |
667 def _gitisancestor(self, r1, r2): |
673 def _gitisancestor(self, r1, r2): |
668 base = self._gitcommand(['merge-base', r1, r2]) |
674 base = self._gitcommand(['merge-base', r1, r2]) |
669 return base == r1 |
675 return base == r1 |
670 |
676 |
671 def _gitbranchmap(self): |
677 def _gitbranchmap(self): |
672 '''returns 3 things: |
678 '''returns 3 things: |
673 the current branch, |
|
674 a map from git branch to revision |
679 a map from git branch to revision |
675 a map from revision to branches''' |
680 a map from revision to branches |
|
681 a map from remote branch to local tracking branch''' |
676 branch2rev = {} |
682 branch2rev = {} |
677 rev2branch = {} |
683 rev2branch = {} |
678 tracking = {} |
684 tracking = {} |
679 current, err = self._gitdir(['symbolic-ref', 'HEAD', '--quiet']) |
|
680 if err: |
|
681 current = None |
|
682 |
|
683 out = self._gitcommand(['for-each-ref', '--format', |
685 out = self._gitcommand(['for-each-ref', '--format', |
684 '%(objectname) %(refname) %(upstream) end']) |
686 '%(objectname) %(refname) %(upstream) end']) |
685 for line in out.split('\n'): |
687 for line in out.split('\n'): |
686 revision, ref, upstream = line.split(' ')[:3] |
688 revision, ref, upstream = line.split(' ')[:3] |
687 if ref.startswith('refs/tags/'): |
689 if ref.startswith('refs/tags/'): |
691 branch2rev[ref] = revision |
693 branch2rev[ref] = revision |
692 rev2branch.setdefault(revision, []).append(ref) |
694 rev2branch.setdefault(revision, []).append(ref) |
693 if upstream: |
695 if upstream: |
694 # assumes no more than one local tracking branch for a remote |
696 # assumes no more than one local tracking branch for a remote |
695 tracking[upstream] = ref |
697 tracking[upstream] = ref |
696 return current, branch2rev, rev2branch, tracking |
698 return branch2rev, rev2branch, tracking |
697 |
699 |
698 def _fetch(self, source, revision): |
700 def _fetch(self, source, revision): |
699 if not os.path.exists('%s/.git' % self._path): |
701 if not os.path.exists('%s/.git' % self._path): |
700 self._ui.status(_('cloning subrepo %s\n') % self._relpath) |
702 self._ui.status(_('cloning subrepo %s\n') % self._relpath) |
701 self._gitnodir(['clone', source, self._path]) |
703 self._gitnodir(['clone', source, self._path]) |
729 if self._gitstate() == revision: |
731 if self._gitstate() == revision: |
730 self._gitcommand(['reset', '--hard', 'HEAD']) |
732 self._gitcommand(['reset', '--hard', 'HEAD']) |
731 return |
733 return |
732 elif self._gitstate() == revision: |
734 elif self._gitstate() == revision: |
733 return |
735 return |
734 current, branch2rev, rev2branch, tracking = self._gitbranchmap() |
736 branch2rev, rev2branch, tracking = self._gitbranchmap() |
735 |
737 |
736 def rawcheckout(): |
738 def rawcheckout(): |
737 # no branch to checkout, check it out with no branch |
739 # no branch to checkout, check it out with no branch |
738 self._ui.warn(_('checking out detached HEAD in subrepo %s\n') % |
740 self._ui.warn(_('checking out detached HEAD in subrepo %s\n') % |
739 self._relpath) |
741 self._relpath) |
774 # if the local tracking branch is downstream of it, |
776 # if the local tracking branch is downstream of it, |
775 # a normal `git pull` would have performed a "fast-forward merge" |
777 # a normal `git pull` would have performed a "fast-forward merge" |
776 # which is equivalent to updating the local branch to the remote. |
778 # which is equivalent to updating the local branch to the remote. |
777 # Since we are only looking at branching at update, we need to |
779 # Since we are only looking at branching at update, we need to |
778 # detect this situation and perform this action lazily. |
780 # detect this situation and perform this action lazily. |
779 if tracking[remote] != current: |
781 if tracking[remote] != self._gitcurrentbranch(): |
780 self._gitcommand(['checkout', tracking[remote]]) |
782 self._gitcommand(['checkout', tracking[remote]]) |
781 self._gitcommand(['merge', '--ff', remote]) |
783 self._gitcommand(['merge', '--ff', remote]) |
782 else: |
784 else: |
783 # a real merge would be required, just checkout the revision |
785 # a real merge would be required, just checkout the revision |
784 rawcheckout() |
786 rawcheckout() |
807 elif base != self._state[1]: |
809 elif base != self._state[1]: |
808 self._gitcommand(['merge', '--no-commit', revision]) |
810 self._gitcommand(['merge', '--no-commit', revision]) |
809 |
811 |
810 def push(self, force): |
812 def push(self, force): |
811 # if a branch in origin contains the revision, nothing to do |
813 # if a branch in origin contains the revision, nothing to do |
812 current, branch2rev, rev2branch, tracking = self._gitbranchmap() |
814 branch2rev, rev2branch, tracking = self._gitbranchmap() |
813 if self._state[1] in rev2branch: |
815 if self._state[1] in rev2branch: |
814 for b in rev2branch[self._state[1]]: |
816 for b in rev2branch[self._state[1]]: |
815 if b.startswith('refs/remotes/origin/'): |
817 if b.startswith('refs/remotes/origin/'): |
816 return True |
818 return True |
817 for b, revision in branch2rev.iteritems(): |
819 for b, revision in branch2rev.iteritems(): |
820 return True |
822 return True |
821 # otherwise, try to push the currently checked out branch |
823 # otherwise, try to push the currently checked out branch |
822 cmd = ['push'] |
824 cmd = ['push'] |
823 if force: |
825 if force: |
824 cmd.append('--force') |
826 cmd.append('--force') |
|
827 |
|
828 current = self._gitcurrentbranch() |
825 if current: |
829 if current: |
826 # determine if the current branch is even useful |
830 # determine if the current branch is even useful |
827 if not self._gitisancestor(self._state[1], current): |
831 if not self._gitisancestor(self._state[1], current): |
828 self._ui.warn(_('unrelated git branch checked out ' |
832 self._ui.warn(_('unrelated git branch checked out ' |
829 'in subrepo %s\n') % self._relpath) |
833 'in subrepo %s\n') % self._relpath) |