source: tailor/vcpx/repository/git/target.py @ 1664

Revision 1664, 12.2 KB checked in by lele@…, 4 years ago (diff)

Little fixes to git target
Thanks to Youness Alaoui (KaKaRoTo?)

RevLine 
[783]1# -*- mode: python; coding: utf-8 -*-
2# :Progetto: vcpx -- Git target (using git-core)
3# :Creato:   Thu  1 Sep 2005 04:01:37 EDT
4# :Autore:   Todd Mokros <tmokros@tmokros.net>
[1012]5#            Brendan Cully <brendan@kublai.com>
[1195]6#            Yann Dirson <ydirson@altern.org>
[783]7# :Licenza:  GNU General Public License
8#
9
10"""
[1211]11This module implements the target backend for Git using git-core.
[783]12"""
13
14__docformat__ = 'reStructuredText'
15
[1297]16from vcpx import TailorException
[1195]17from vcpx.config import ConfigurationError
[1297]18from vcpx.repository.git import GitExternalCommand, PIPE
19from vcpx.source import ChangesetApplicationFailure
[1179]20from vcpx.target import SynchronizableTargetWorkingDir, TargetInitializationFailure
[1216]21from vcpx.tzinfo import FixedOffset
[1199]22
[1215]23
[1199]24class BranchpointFailure(TailorException):
25    "Specified branchpoint not found in parent branch"
[1179]26
[783]27
[1211]28class GitTargetWorkingDir(SynchronizableTargetWorkingDir):
[783]29
30    def _addPathnames(self, names):
31        """
32        Add some new filesystem objects.
33        """
34
35        from os.path import join, isdir
36
37        # Currently git does not handle directories at all, so filter
38        # them out.
39
[1209]40        notdirs = [n for n in names if not isdir(join(self.repository.basedir, n))]
[783]41        if notdirs:
[1219]42            self.repository.runCommand(['update-index', '--add'] + notdirs)
[1176]43
[1191]44    def _editPathnames(self, names):
[1176]45        """
46        Records a sequence of filesystem objects as updated.
47        """
48
[1410]49        from os.path import join, isdir
50
51        # can we assume we don't have directories in the list ?  Nope.
52
53        notdirs = [n for n in names if not isdir(join(self.repository.basedir, n))]
54        if notdirs:
55            self.repository.runCommand(['update-index'] + notdirs)
[783]56
57    def __parse_author(self, author):
58        """
59        Parse the author field, returning (name, email)
60        """
[1307]61
[783]62        from email.Utils import parseaddr
[1179]63        from vcpx.target import AUTHOR, HOST
[783]64
65        if author.find('@') > -1:
66            name, email = parseaddr(author)
67        else:
68            name, email = author, ''
69        name = name.strip()
70        email = email.strip()
71        if not name:
72            name = AUTHOR
73        if not email:
[1433]74            email = "%s@%s" % (name, HOST)
[783]75        return (name, email)
76
[1343]77    def _commit(self, date, author, patchname, changelog=None, entries=None,
78                tags=[], isinitialcommit=False):
[783]79        """
80        Commit the changeset.
81        """
82
[1212]83        from os import environ
84
[1434]85        try:
86            self.repository.runCommand(['status'])
87        except Exception, e:
88            self.log.info("git-status returned an error---assuming nothing to do")
89            return
90
[1016]91        encode = self.repository.encode
92
[783]93        logmessage = []
94        if patchname:
[850]95            logmessage.append(patchname)
[783]96        if changelog:
[850]97            logmessage.append(changelog)
[783]98
[1210]99        env = {}
100        env.update(environ)
101
[1474]102        # update the index
103        self.repository.runCommand(['add', '-u'])
[1219]104        treeid = self.repository.runCommand(['write-tree'])[0]
[1181]105
[1195]106        # in single-repository mode, only update the relevant branch
[1277]107        if self.repository.branch_name:
108            refname = self.repository.branch_name
[1195]109        else:
110            refname = 'HEAD'
111
112        # find the previous commit on the branch if any
[1209]113        c = GitExternalCommand(self.repository, cwd=self.repository.basedir,
[1195]114                               command=self.repository.command('rev-parse', refname))
[1181]115        (out, err) = c.execute(stdout=PIPE, stderr=PIPE)
116        if c.exit_status:
[1182]117            # Do we need to check err to be sure there was no error ?
118            self.log.info("Doing initial commit")
119            parent = False
[1181]120        else:
[1182]121            # FIXME: I'd prefer to avoid all those "if parent"
122            parent = out.read().split('\n')[0]
[1181]123
[783]124        (name, email) = self.__parse_author(author)
125        if name:
[1016]126            env['GIT_AUTHOR_NAME'] = encode(name)
127            env['GIT_COMMITTER_NAME'] = encode(name)
[783]128        if email:
129            env['GIT_AUTHOR_EMAIL']=email
[995]130            env['GIT_COMMITTER_EMAIL']=email
[783]131        if date:
[1216]132            env['GIT_AUTHOR_DATE']=date.strftime("%Y-%m-%d %H:%M:%S %z")
[995]133            env['GIT_COMMITTER_DATE']=env['GIT_AUTHOR_DATE']
[1182]134        if parent:
[1181]135            cmd = self.repository.command('commit-tree', treeid, '-p', parent)
[1182]136        else:
[1181]137            cmd = self.repository.command('commit-tree', treeid)
[1209]138        c = GitExternalCommand(self.repository, cwd=self.repository.basedir, command=cmd)
[783]139
[1016]140        logmessage = encode('\n'.join(logmessage))
[1122]141        if not logmessage:
142            logmessage = 'No commit message\n'
[995]143        if not logmessage.endswith('\n'):
144            logmessage += '\n'
145        (out, _) = c.execute(stdout=PIPE, env=env, input=logmessage)
[783]146        if c.exit_status:
[1163]147            failed = True
148            if out:
149                for line in [x.strip() for x in out if x[0] != '#']:
150                    if line == 'nothing to commit':
151                        failed = False
152            if failed:
[786]153                raise ChangesetApplicationFailure("%s returned status %d" %
154                                                  (str(c), c.exit_status))
[1182]155        else:
156            commitid=out.read().split('\n')[0]
[1195]157
[1182]158            if parent:
[1219]159                self.repository.runCommand(['update-ref', refname, commitid, parent])
[1181]160            else:
[1219]161                self.repository.runCommand(['update-ref', refname, commitid])
[1003]162
[1390]163    def _tag(self, tag, date, author):
[1357]164
165        # in single-repository mode, only update the relevant branch
166        if self.repository.branch_name:
167            refname = self.repository.branch_name
168        else:
169            refname = 'HEAD'
170
[1003]171        # Allow a new tag to overwrite an older one with -f
[1462]172        args = ["tag", "-a",]
173        if self.repository.overwrite_tags:
[1603]174            args.append("-f")
[1357]175
[1462]176        # Escape the tag name for git
177        import re
178        tag_git = re.sub('_*$', '', re.sub('__', '_', re.sub('[^A-Za-z0-9_-]', '_', tag)))
[1357]179
[1462]180        args += ["-m", tag, tag_git, refname]
[1357]181        cmd = self.repository.command(*args)
[1209]182        c = GitExternalCommand(self.repository, cwd=self.repository.basedir, command=cmd)
[1462]183        from os import environ
[1461]184        env = {}
185        env.update(environ)
186        (name, email) = self.__parse_author(author)
187        if name:
188            env['GIT_AUTHOR_NAME'] = self.repository.encode(name)
189            env['GIT_COMMITTER_NAME'] = self.repository.encode(name)
190        if email:
191            env['GIT_AUTHOR_EMAIL']=email
192            env['GIT_COMMITTER_EMAIL']=email
193        if date:
194            env['GIT_AUTHOR_DATE']=date.strftime("%Y-%m-%d %H:%M:%S %z")
195            env['GIT_COMMITTER_DATE']=env['GIT_AUTHOR_DATE']
196        c.execute(env=env)
[1003]197
198        if c.exit_status:
[1603]199            if not self.repository.overwrite_tags:
200                self.log.critical("Couldn't set tag '%s': maybe it's a "
201                                  "conflict with a previous tag, and "
202                                  "overwrite-tags=True may help" %
203                                  tag_git)
[1003]204            raise ChangesetApplicationFailure("%s returned status %d" %
205                                              (str(c), c.exit_status))
[783]206
207    def _removePathnames(self, names):
208        """
209        Remove some filesystem object.
210        """
[1176]211
212        from os.path import join, isdir
213
214        # Currently git does not handle directories at all, so filter
215        # them out.
216
[1209]217        notdirs = [n for n in names if not isdir(join(self.repository.basedir, n))]
[1176]218        if notdirs:
[1664]219            self.repository.runCommand(['rm'] + notdirs)
[783]220
221    def _renamePathname(self, oldname, newname):
222        """
223        Rename a filesystem object.
224        """
[1307]225
[1463]226        # Git does not seem to allow
227        #   $ mv a.txt b.txt
[1466]228        #   $ git mv a.txt b.txt
[1463]229        # Here we are in this situation, since upstream VCS already
230        # moved the item.
[1307]231
[1473]232        from os import mkdir, rename, rmdir, listdir
233        from os.path import join, exists, isdir
[783]234
[1463]235        oldpath = join(self.repository.basedir, oldname)
236        newpath = join(self.repository.basedir, newname)
[783]237
[1652]238        # These are used with disjunct directories.
239        newpathtmp = newpath + '-TAILOR-HACKED-TEMP-NAME'
240        newnametmp = newname + '-TAILOR-HACKED-TEMP-NAME'
241
[1473]242        # Git does not track empty directories, so if there is only an
243        # empty dir, we have nothing to do.
[1652]244        if (isdir(newpath) and not len(listdir(newpath))) or \
245           (isdir(newpathtmp) and not len(listdir(newpathtmp))):
[1473]246            return
247
[1470]248        # rename() won't work for rename(a/b, a)
[1472]249        if newpath.startswith(oldpath+"/"):
[1470]250            oldpathtmp = oldpath+"-TAILOR-HACKED-TEMP-NAME"
251            oldnametmp = oldname+"-TAILOR-HACKED-TEMP-NAME"
252            if exists(oldpathtmp):
253                rename(oldpathtmp, oldpath)
254            rename(newpath, oldpathtmp)
255            rmdir(oldpath)
256            rename(oldpathtmp, oldpath)
257            mkdir(oldpathtmp)
258            self.repository.runCommand(['mv', oldname, newname.replace(oldname, oldnametmp, 1)])
259            self.repository.runCommand(['mv', oldnametmp, oldname])
260        else:
[1651]261            if self.shared_basedirs:
[1664]262                # Recent gits handle this correctly
263                self.repository.runCommand(['mv', oldname, newname])
[1651]264            else:
265                # For disjunct directories, the real new entry has been moved
266                # out of the way, and the superclass expects us to rename the
267                # the file or directory via git.
[1653]268
269                # First, some sanity checks.
270                if exists(newpath):
271                    raise ChangesetApplicationFailure(
272                        "Cannot rename since target already exists: %s" % newname)
273
274                if not exists(newpathtmp):
275                    raise ChangesetApplicationFailure(
276                        "Cannot rename since actual target not found: %s" % newnametmp)
277
[1654]278                if exists(oldpath):
279                    # Under normal operation, just git-mv the old name to the new
280                    # name.  Git will notice the changes too.
281                    self.repository.runCommand(['mv', oldname, newname])
282                else:
283                    # If a revision renames directory A/ to B/, plus file A/a to
284                    # B/b, *and if* A -> B happened already, then the superclass
285                    # already made B/b.  We cannot git-mv B/a to B/b since B/a is
286                    # gone.  The workaround is git-add B/b-TAILOR-HACKED-TEMP-NAME,
287                    # then git-mv it to B/b.
288                    self.repository.runCommand(['add', newnametmp])
289                    self.repository.runCommand(['mv', newnametmp, newname])
[783]290
291    def _prepareTargetRepository(self):
[1215]292        self.repository.create()
[783]293
294    def _prepareWorkingDirectory(self, source_repo):
295        """
296        Create the .git/info/exclude.
297        """
298
[998]299        from os.path import join, exists
300        from os import mkdir
[1179]301        from vcpx.dualwd import IGNORED_METADIRS
[783]302
[1195]303        # create info/excludes in storagedir
[1209]304        infodir = join(self.repository.basedir, self.repository.storagedir, 'info')
[998]305        if not exists(infodir):
306            mkdir(infodir)
307
[783]308        # Create the .git/info/exclude file, that contains an
309        # fnmatch per line with metadirs to be skipped.
[998]310        ignore = open(join(infodir, 'exclude'), 'a')
[783]311        ignore.write('\n')
312        ignore.write('\n'.join(['%s' % md
313                                for md in IGNORED_METADIRS]))
314        ignore.write('\n')
[1209]315        if self.logfile.startswith(self.repository.basedir):
316            ignore.write(self.logfile[len(self.repository.basedir)+1:])
[783]317            ignore.write('\n')
[1209]318        if self.state_file.filename.startswith(self.repository.basedir):
319            sfrelname = self.state_file.filename[len(self.repository.basedir)+1:]
[783]320            ignore.write(sfrelname)
[992]321            ignore.write('\n')
322            ignore.write(sfrelname+'.old')
[783]323            ignore.write('\n')
324            ignore.write(sfrelname+'.journal')
325            ignore.write('\n')
326        ignore.close()
327
[1188]328    def importFirstRevision(self, source_repo, changeset, initial):
329        # If we have a parent repository, always track from INITIAL
330        SynchronizableTargetWorkingDir.importFirstRevision(
331            self, source_repo, changeset,
[1277]332            initial or self.repository.branch_point)
Note: See TracBrowser for help on using the repository browser.