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

Revision 1433, 9.4 KB checked in by bruce.stephens@…, 6 years ago (diff)

git email should use name

Line 
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>
5#            Brendan Cully <brendan@kublai.com>
6#            Yann Dirson <ydirson@altern.org>
7# :Licenza:  GNU General Public License
8#
9
10"""
11This module implements the target backend for Git using git-core.
12"""
13
14__docformat__ = 'reStructuredText'
15
16from vcpx import TailorException
17from vcpx.config import ConfigurationError
18from vcpx.repository.git import GitExternalCommand, PIPE
19from vcpx.source import ChangesetApplicationFailure
20from vcpx.target import SynchronizableTargetWorkingDir, TargetInitializationFailure
21from vcpx.tzinfo import FixedOffset
22
23
24class BranchpointFailure(TailorException):
25    "Specified branchpoint not found in parent branch"
26
27
28class GitTargetWorkingDir(SynchronizableTargetWorkingDir):
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
40        notdirs = [n for n in names if not isdir(join(self.repository.basedir, n))]
41        if notdirs:
42            self.repository.runCommand(['update-index', '--add'] + notdirs)
43
44    def _editPathnames(self, names):
45        """
46        Records a sequence of filesystem objects as updated.
47        """
48
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)
56
57    def __parse_author(self, author):
58        """
59        Parse the author field, returning (name, email)
60        """
61
62        from email.Utils import parseaddr
63        from vcpx.target import AUTHOR, HOST
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:
74            email = "%s@%s" % (name, HOST)
75        return (name, email)
76
77    def _commit(self, date, author, patchname, changelog=None, entries=None,
78                tags=[], isinitialcommit=False):
79        """
80        Commit the changeset.
81        """
82
83        from os import environ
84
85        encode = self.repository.encode
86
87        logmessage = []
88        if patchname:
89            logmessage.append(patchname)
90        if changelog:
91            logmessage.append(changelog)
92
93        env = {}
94        env.update(environ)
95
96        treeid = self.repository.runCommand(['write-tree'])[0]
97
98        # in single-repository mode, only update the relevant branch
99        if self.repository.branch_name:
100            refname = self.repository.branch_name
101        else:
102            refname = 'HEAD'
103
104        # find the previous commit on the branch if any
105        c = GitExternalCommand(self.repository, cwd=self.repository.basedir,
106                               command=self.repository.command('rev-parse', refname))
107        (out, err) = c.execute(stdout=PIPE, stderr=PIPE)
108        if c.exit_status:
109            # Do we need to check err to be sure there was no error ?
110            self.log.info("Doing initial commit")
111            parent = False
112        else:
113            # FIXME: I'd prefer to avoid all those "if parent"
114            parent = out.read().split('\n')[0]
115
116        (name, email) = self.__parse_author(author)
117        if name:
118            env['GIT_AUTHOR_NAME'] = encode(name)
119            env['GIT_COMMITTER_NAME'] = encode(name)
120        if email:
121            env['GIT_AUTHOR_EMAIL']=email
122            env['GIT_COMMITTER_EMAIL']=email
123        if date:
124            env['GIT_AUTHOR_DATE']=date.strftime("%Y-%m-%d %H:%M:%S %z")
125            env['GIT_COMMITTER_DATE']=env['GIT_AUTHOR_DATE']
126        if parent:
127            cmd = self.repository.command('commit-tree', treeid, '-p', parent)
128        else:
129            cmd = self.repository.command('commit-tree', treeid)
130        c = GitExternalCommand(self.repository, cwd=self.repository.basedir, command=cmd)
131
132        logmessage = encode('\n'.join(logmessage))
133        if not logmessage:
134            logmessage = 'No commit message\n'
135        if not logmessage.endswith('\n'):
136            logmessage += '\n'
137        (out, _) = c.execute(stdout=PIPE, env=env, input=logmessage)
138        if c.exit_status:
139            failed = True
140            if out:
141                for line in [x.strip() for x in out if x[0] != '#']:
142                    if line == 'nothing to commit':
143                        failed = False
144            if failed:
145                raise ChangesetApplicationFailure("%s returned status %d" %
146                                                  (str(c), c.exit_status))
147        else:
148            commitid=out.read().split('\n')[0]
149
150            if parent:
151                self.repository.runCommand(['update-ref', refname, commitid, parent])
152            else:
153                self.repository.runCommand(['update-ref', refname, commitid])
154
155    def _tag(self, tag, date, author):
156
157        # in single-repository mode, only update the relevant branch
158        if self.repository.branch_name:
159            refname = self.repository.branch_name
160        else:
161            refname = 'HEAD'
162
163        # Allow a new tag to overwrite an older one with -f
164        args = ["tag", "-a",]
165        if self.repository.overwrite_tags:
166                args.append("-f")
167
168        # Escape the tag name for git
169        import re
170        tag_git = re.sub('_*$', '', re.sub('__', '_', re.sub('[^A-Za-z0-9_-]', '_', tag)))
171
172        args += ["-m", tag, tag_git, refname]
173        cmd = self.repository.command(*args)
174        c = GitExternalCommand(self.repository, cwd=self.repository.basedir, command=cmd)
175        c.execute()
176
177        if c.exit_status:
178            raise ChangesetApplicationFailure("%s returned status %d" %
179                                              (str(c), c.exit_status))
180
181    def _removePathnames(self, names):
182        """
183        Remove some filesystem object.
184        """
185
186        from os.path import join, isdir
187
188        # Currently git does not handle directories at all, so filter
189        # them out.
190
191        notdirs = [n for n in names if not isdir(join(self.repository.basedir, n))]
192        if notdirs:
193            self.repository.runCommand(['update-index', '--remove'] + notdirs)
194
195    def _renamePathname(self, oldname, newname):
196        """
197        Rename a filesystem object.
198        """
199
200        # In the future, we may want to switch to using
201        # git rename, in case renames ever get more support
202        # in git.  It currently just does an add and remove.
203
204        from os.path import join, isdir
205        from os import walk
206        from vcpx.dualwd import IGNORED_METADIRS
207
208        from vcpx.shwrap import ExternalCommand
209        cp = ExternalCommand(command=['cp'])
210        if isdir(join(self.repository.basedir, newname)) or isdir(join(self.repository.basedir, oldname)):
211            # Given lack of support for directories in current Git,
212            # loop over all files under the new directory and
213            # do a add/remove on them.
214            skip = len(self.repository.basedir)+len(newname)+2
215            for dir, subdirs, files in walk(join(self.repository.basedir, newname)):
216                prefix = dir[skip:]
217
218                for excd in IGNORED_METADIRS:
219                    if excd in subdirs:
220                        subdirs.remove(excd)
221
222                for f in files:
223                    self._removePathnames([join(oldname, prefix, f)])
224                    self._addPathnames([join(newname, prefix, f)])
225        else:
226            cp.execute(join(self.repository.basedir, oldname), join(self.repository.basedir, newname))
227            self._removePathnames([oldname])
228            self._addPathnames([newname])
229
230    def _prepareTargetRepository(self):
231        self.repository.create()
232
233    def _prepareWorkingDirectory(self, source_repo):
234        """
235        Create the .git/info/exclude.
236        """
237
238        from os.path import join, exists
239        from os import mkdir
240        from vcpx.dualwd import IGNORED_METADIRS
241
242        # create info/excludes in storagedir
243        infodir = join(self.repository.basedir, self.repository.storagedir, 'info')
244        if not exists(infodir):
245            mkdir(infodir)
246
247        # Create the .git/info/exclude file, that contains an
248        # fnmatch per line with metadirs to be skipped.
249        ignore = open(join(infodir, 'exclude'), 'a')
250        ignore.write('\n')
251        ignore.write('\n'.join(['%s' % md
252                                for md in IGNORED_METADIRS]))
253        ignore.write('\n')
254        if self.logfile.startswith(self.repository.basedir):
255            ignore.write(self.logfile[len(self.repository.basedir)+1:])
256            ignore.write('\n')
257        if self.state_file.filename.startswith(self.repository.basedir):
258            sfrelname = self.state_file.filename[len(self.repository.basedir)+1:]
259            ignore.write(sfrelname)
260            ignore.write('\n')
261            ignore.write(sfrelname+'.old')
262            ignore.write('\n')
263            ignore.write(sfrelname+'.journal')
264            ignore.write('\n')
265        ignore.close()
266
267    def importFirstRevision(self, source_repo, changeset, initial):
268        # If we have a parent repository, always track from INITIAL
269        SynchronizableTargetWorkingDir.importFirstRevision(
270            self, source_repo, changeset,
271            initial or self.repository.branch_point)
Note: See TracBrowser for help on using the repository browser.