Changeset 1216 in tailor


Ignore:
Timestamp:
07/06/06 17:29:20 (7 years ago)
Author:
Adeodato Simo <dato@…>
Hash name:
20060706152920-d6905-8960cdcccc5a3f12129b7cc60291cb58c183d98b
Message:

[general] Ensure Changeset.date always has timezone information

This patch modifies all source backends to always set the tzinfo member of
every Changeset.date they create, and all target backends to make proper use
of it at commit time. This should solve all offset errors in dates for
commits, and making tailor robust with respect implementing a "timezone"
option for a project.

Summary of the needed changes:

+ vcpx:

  • tzinfo.py: new file, taken from pytz sources. Provides definitions for two basic tzinfo classes: UTC, and FixedOffset?.
  • changes.py: make "date" member a property, with a setter function that raises an exception if the provided date does not have a not-None tzinfo member. Prefer this to silently setting it to UTC, which may be a wrong assumption.

+ vcpx/repository:

  • [source] cvs.py, cvsps.py darcs.py, monotone.py, svn.py: Changeset.date was always created in UTC; explicitly set date.tzinfo to UTC from tzinfo.py.
  • [source] bzr.py, git.py, hg.py: an UTC date was created from timestamp and offset; instead, create a date in the proper FixedOffset? timezone.
  • [source] tla.py: an UTC date was created from the Standard-date header; however, a Date header with the local date is also provided: add new function parse_date() that can calculate the timezone from these two headers, and use it.
  • [target] cvs.py, cvsps.py: str(date) was assumed not to contain timezone information; make date a tzinfo-less datetime prior to str()'ing it.
  • [target] cdv.py, darcs.py, monotone.py, svn.py: Changeset.date was assumed to be UTC; make the appropriate conversion prior to using it.
  • [target] cg.py, git.py: include "%z" in the strftime format specifier for GIT_AUTHOR_DATE.
  • [target] bzr.py, hg.py: do not use time.mktime() to calculate the timestamp, since it takes into account local timezone; use calendar.timegm() instead, which gives an UTC timestamp. And provide an appropriate timezone to the underlying commit() function.

+ vcpx/tests:

  • cvs.py, cvsps.py, darcs.py, svn.py: set tzinfo=UTC in datetimes that get created to be compared to cset.date.
Location:
vcpx
Files:
1 added
16 edited

Legend:

Unmodified
Added
Removed
  • vcpx/changes.py

    r1162 r1216  
    100100    REFILL_MESSAGE = False 
    101101    """Refill changelogs""" 
     102 
     103    def _get_date(self): 
     104        return self.__date 
     105 
     106    def _set_date(self, date): 
     107        if date and date.tzinfo is None: 
     108            raise "Tailor bug (please report): Changeset dates must have a timezone." 
     109        self.__date = date 
     110 
     111    # date has to be a property because some backends (eg. monotone) 
     112    # update it after the constructor 
     113    date = property(_get_date, _set_date) 
    102114 
    103115    def __init__(self, revision, date, author, log, entries=None, **other): 
  • vcpx/repository/cvsps.py

    r1215 r1216  
    2020                        InvocationError 
    2121from vcpx.target import SynchronizableTargetWorkingDir, TargetInitializationFailure 
     22from vcpx.tzinfo import UTC 
    2223 
    2324 
     
    208209            y,m,d = map(int, cvsdate[:10].split('/')) 
    209210            hh,mm,ss = map(int, cvsdate[11:19].split(':')) 
    210             timestamp = datetime(y, m, d, hh, mm, ss) 
     211            timestamp = datetime(y, m, d, hh, mm, ss, 0, UTC) 
    211212            pset['date'] = timestamp 
    212213 
     
    403404        if timestamp == 'INITIAL': 
    404405            initialcset = csets.next() 
    405             timestamp = initialcset.date.isoformat(sep=' ') 
     406            timestamp = initialcset.date.replace(tzinfo=None).isoformat(sep=' ') 
    406407        else: 
    407408            initialcset = None 
  • vcpx/repository/darcs.py

    r1215 r1216  
    1919                        GetUpstreamChangesetsFailure 
    2020from vcpx.target import SynchronizableTargetWorkingDir, TargetInitializationFailure 
     21from vcpx.tzinfo import UTC 
    2122 
    2223 
     
    146147                    # Old darcs patches use the form Sun Oct 20 20:01:05 EDT 2002 
    147148                    timestamp = datetime(*strptime(date[:19] + date[-5:], '%a %b %d %H:%M:%S %Y')[:6]) 
     149 
     150                timestamp = timestamp.replace(tzinfo=UTC) # not true for the ValueError case, but oh well 
     151 
    148152                self.current['date'] = timestamp 
    149153                self.current['comment'] = '' 
     
    308312                author = l[30:-1] 
    309313                y,m,d,hh,mm,ss,d1,d2,d3 = strptime(date, "%a %b %d %H:%M:%S %Z %Y") 
    310                 date = datetime(y,m,d,hh,mm,ss) 
     314                date = datetime(y,m,d,hh,mm,ss,0,UTC) 
    311315                l = output.readline() 
    312316                assert (l.startswith('  * ') or 
     
    550554        logmessage = [] 
    551555 
    552         logmessage.append(date.strftime('%Y/%m/%d %H:%M:%S UTC')) 
     556        logmessage.append(date.astimezone(UTC).strftime('%Y/%m/%d %H:%M:%S UTC')) 
    553557        logmessage.append(author) 
    554558        if patchname: 
  • vcpx/repository/svn.py

    r1215 r1216  
    1717from vcpx.target import SynchronizableTargetWorkingDir, TargetInitializationFailure 
    1818from vcpx.config import ConfigurationError 
     19from vcpx.tzinfo import UTC 
    1920 
    2021 
     
    259260                hh,mm,ss = map(int, svndate[11:19].split(':')) 
    260261                ms = int(svndate[20:-1]) 
    261                 timestamp = datetime(y, m, d, hh, mm, ss, ms) 
     262                timestamp = datetime(y, m, d, hh, mm, ss, ms, UTC) 
    262263 
    263264                changeset = Changeset(self.current['revision'], 
     
    580581            propset = ExternalCommand(cwd=self.repository.basedir, command=cmd) 
    581582 
     583            date = date.astimezone(UTC).replace(microsecond = 0, tzinfo=None) 
    582584            propset.execute(date.isoformat()+".000000Z", propname='svn:date') 
    583585            propset.execute(encode(author), propname='svn:author') 
  • vcpx/tests/cvsps.py

    r1179 r1216  
    99from datetime import datetime 
    1010from vcpx.repository.cvsps import changesets_from_cvsps 
     11from vcpx.tzinfo import UTC 
    1112 
    1213 
     
    3031        self.assertEqual(cset.revision, '1500') 
    3132        self.assertEqual(cset.author, "grubert") 
    32         self.assertEqual(cset.date, datetime(2004, 5, 9, 17, 54, 22)) 
     33        self.assertEqual(cset.date, datetime(2004, 5, 9, 17, 54, 22, 0, UTC)) 
    3334        self.assertEqual(cset.log, "Tell the reason for using mbox " 
    3435                                   "(not wrapping long lines).") 
  • vcpx/repository/cvs.py

    r1215 r1216  
    1616from vcpx.source import GetUpstreamChangesetsFailure 
    1717from vcpx.config import ConfigurationError 
     18from vcpx.tzinfo import UTC 
    1819 
    1920 
     
    152153    """ 
    153154 
    154     return "%s by %s" % (timestamp, author) 
     155    # don't print timezone info, to remain compatible (does not buy us 
     156    # anything, it being always UTC) 
     157    return "%s by %s" % (timestamp.replace(tzinfo=None), author) 
    155158 
    156159def _splitGlobalCVSRevision(revision): 
     
    285288        y,m,d = map(int, day.split(day[4])) 
    286289        hh,mm,ss = map(int, time.split(':')) 
    287         date = datetime(y,m,d,hh,mm,ss) 
     290        date = datetime(y,m,d,hh,mm,ss,0,UTC) 
    288291 
    289292        assert info[1].strip()[:8] == 'author: ', infoline 
     
    469472                # probably get a better date by looking at when the 
    470473                # files were added, but who cares. 
    471                 timestamp = datetime(1900,1,1) 
     474                timestamp = datetime(1900,1,1).replace(tzinfo=UTC) 
    472475            else: 
    473476                # "since" is a revision name read from the state file, 
     
    477480                # call to timestamp.__str__() in getGlobalCVSRevision. 
    478481                y,m,d,hh,mm,ss,d1,d2,d3 = strptime(since, "%Y-%m-%d %H:%M:%S") 
    479                 timestamp = datetime(y,m,d,hh,mm,ss) 
     482                timestamp = datetime(y,m,d,hh,mm,ss,0,UTC) 
    480483            author = "unknown tagger" 
    481484            changelog = "tag %s %s" % (timestamp, tags) 
     
    668671 
    669672        if ts == 'Result of merge': 
    670             self.timestamp = datetime.today() 
     673            self.timestamp = datetime.now(tz=UTC) 
    671674        else: 
    672675            if ts.startswith('Result of merge+'): 
    673676                ts = ts[16:] 
    674677            y,m,d,hh,mm,ss,d1,d2,d3 = strptime(ts, "%a %b %d %H:%M:%S %Y") 
    675             self.timestamp = datetime(y,m,d,hh,mm,ss) 
     678            self.timestamp = datetime(y,m,d,hh,mm,ss,0,UTC) 
    676679 
    677680        self.cvs_tag = tag 
  • vcpx/tests/cvs.py

    r1183 r1216  
    1111from vcpx.repository.cvs import changesets_from_cvslog, compare_cvs_revs, \ 
    1212                                cvs_revs_same_branch, normalize_cvs_rev 
     13from vcpx.tzinfo import UTC 
    1314 
    1415 
     
    2627        self.assertEqual(e.filename, 'version.txt') 
    2728        self.assertEqual(e.cvs_version, '1.16.2.1') 
    28         self.assertEqual(e.timestamp, datetime(2004, 7, 13, 12, 49, 2)) 
     29        self.assertEqual(e.timestamp, datetime(2004, 7, 13, 12, 49, 2, 0, UTC)) 
    2930        self.assertEqual(e.cvs_tag, 'T1.16.2.1') 
    3031 
     
    3334        self.assertEqual(e.filename, 'Validator.py') 
    3435        self.assertEqual(e.cvs_version, '1.31.2.5') 
    35         self.assertEqual(e.timestamp, datetime(2004, 7, 13, 13, 43, 6)) 
     36        self.assertEqual(e.timestamp, datetime(2004, 7, 13, 13, 43, 6, 0, UTC)) 
    3637        self.assertEqual(e.cvs_tag, 'T1.31.2.5') 
    3738 
     
    4041        self.assertEqual(e.filename, 'Makefile.am') 
    4142        self.assertEqual(e.cvs_version, '1.55') 
    42         self.assert_((datetime.today() - e.timestamp) < timedelta(seconds=1)) 
     43        self.assert_((datetime.now(tz=UTC) - e.timestamp) < timedelta(seconds=1)) 
    4344        self.assertEqual(e.cvs_tag, 'T1.55') 
    4445 
     
    6465        cset = csets[0] 
    6566        self.assertEqual(cset.author, "goodger") 
    66         self.assertEqual(cset.date, datetime(2004, 6, 3, 13, 50, 58)) 
     67        self.assertEqual(cset.date, datetime(2004, 6, 3, 13, 50, 58, 0, UTC)) 
    6768        self.assertEqual(cset.log, "Added to project (exctracted from " 
    6869                                   "HISTORY.txt)") 
     
    7475        cset = csets[1] 
    7576        self.assertEqual(cset.author, "goodger") 
    76         self.assertEqual(cset.date, datetime(2004, 6, 10, 2, 17, 20)) 
     77        self.assertEqual(cset.date, datetime(2004, 6, 10, 2, 17, 20, 0, UTC)) 
    7778        self.assertEqual(cset.log, "") 
    7879        entry = cset.entries[0] 
     
    8990        cset = csets.next() 
    9091        self.assertEqual(cset.author, "goodger") 
    91         self.assertEqual(cset.date, datetime(2004, 4, 27, 19, 51, 07)) 
    92  
    93         cset = csets.next() 
    94         self.assertEqual(cset.author, "goodger") 
    95         self.assertEqual(cset.date, datetime(2004, 6, 17, 2, 8, 48)) 
    96  
    97         cset = csets.next() 
    98         self.assertEqual(cset.author, "goodger") 
    99         self.assertEqual(cset.date, datetime(2004, 6, 17, 2, 51, 31)) 
    100  
    101         cset = csets.next() 
    102         self.assertEqual(cset.author, "goodger") 
    103         self.assertEqual(cset.date, datetime(2004, 6, 17, 21, 46, 50)) 
     92        self.assertEqual(cset.date, datetime(2004, 4, 27, 19, 51, 07, 0, UTC)) 
     93 
     94        cset = csets.next() 
     95        self.assertEqual(cset.author, "goodger") 
     96        self.assertEqual(cset.date, datetime(2004, 6, 17, 2, 8, 48, 0, UTC)) 
     97 
     98        cset = csets.next() 
     99        self.assertEqual(cset.author, "goodger") 
     100        self.assertEqual(cset.date, datetime(2004, 6, 17, 2, 51, 31, 0, UTC)) 
     101 
     102        cset = csets.next() 
     103        self.assertEqual(cset.author, "goodger") 
     104        self.assertEqual(cset.date, datetime(2004, 6, 17, 21, 46, 50, 0, UTC)) 
    104105        self.assertEqual(cset.log,"support for CSV directive implementation") 
    105106        self.assertEqual(len(cset.entries), 2) 
     
    115116        cset = csets.next() 
    116117        self.assertEqual(cset.author, "felixwiemann") 
    117         self.assertEqual(cset.date, datetime(2004, 6, 20, 16, 3, 17)) 
     118        self.assertEqual(cset.date, datetime(2004, 6, 20, 16, 3, 17, 0, UTC)) 
    118119 
    119120    def testDeletedEntry(self): 
     
    144145        cset = csets[0] 
    145146        self.assertEqual(len(cset.entries), 2) 
    146         self.assertEqual(cset.date, datetime(1996, 10, 7, 18, 32, 12)) 
     147        self.assertEqual(cset.date, datetime(1996, 10, 7, 18, 32, 12, 0, UTC)) 
    147148 
    148149        cset = csets[1] 
    149150        self.assertEqual(len(cset.entries), 1) 
    150         self.assertEqual(cset.date, datetime(1996, 10, 14, 13, 56, 50)) 
     151        self.assertEqual(cset.date, datetime(1996, 10, 14, 13, 56, 50, 0, UTC)) 
    151152        entry = cset.entries[0] 
    152153        self.assertEqual(entry.name, 'Doc/libObjCStreams.tex') 
     
    154155        cset = csets[2] 
    155156        self.assertEqual(len(cset.entries), 1) 
    156         self.assertEqual(cset.date, datetime(1996, 10, 18, 12, 36, 4)) 
     157        self.assertEqual(cset.date, datetime(1996, 10, 18, 12, 36, 4, 0, UTC)) 
    157158        entry = cset.entries[0] 
    158159        self.assertEqual(entry.name, 'Doc/libPyObjC.tex') 
     
    160161        cset = csets[3] 
    161162        self.assertEqual(len(cset.entries), 2) 
    162         self.assertEqual(cset.date, datetime(1996, 10, 18, 13, 48, 45)) 
     163        self.assertEqual(cset.date, datetime(1996, 10, 18, 13, 48, 45, 0, UTC)) 
    163164 
    164165    def testBranchesInLog(self): 
     
    193194        cset = csets.next() 
    194195        self.assertEqual(cset.author, "tiran") 
    195         self.assertEqual(cset.date, datetime(2004, 8, 6, 20, 13, 30)) 
     196        self.assertEqual(cset.date, datetime(2004, 8, 6, 20, 13, 30, 0, UTC)) 
    196197        self.assertEqual(cset.log, "Added ExtendingType") 
    197198        entry = cset.entries[0] 
     
    202203        cset = csets.next() 
    203204        self.assertEqual(cset.author, "tiran") 
    204         self.assertEqual(cset.date, datetime(2004, 8, 9, 7, 44, 9)) 
     205        self.assertEqual(cset.date, datetime(2004, 8, 9, 7, 44, 9, 0, UTC)) 
    205206        self.assertEqual(cset.log, """\ 
    206207Recoded migration walkers to use a generator instead returning a list to make them much more memory efficient. 
     
    216217        cset = csets.next() 
    217218        self.assertEqual(cset.author, "tiran") 
    218         self.assertEqual(cset.date, datetime(2004, 8, 13, 13, 15, 46)) 
     219        self.assertEqual(cset.date, datetime(2004, 8, 13, 13, 15, 46, 0, UTC)) 
    219220        self.assertEqual(cset.log, "Fixed typo") 
    220221 
    221222        cset = csets.next() 
    222223        self.assertEqual(cset.author, "tiran") 
    223         self.assertEqual(cset.date, datetime(2004, 8, 13, 13, 21, 24)) 
     224        self.assertEqual(cset.date, datetime(2004, 8, 13, 13, 21, 24, 0, UTC)) 
    224225        self.assertEqual(cset.log, "Something went wrong ...") 
    225226 
    226227        cset = csets.next() 
    227228        self.assertEqual(cset.author, "tiran") 
    228         self.assertEqual(cset.date, datetime(2004, 8, 13, 13, 21, 53)) 
     229        self.assertEqual(cset.date, datetime(2004, 8, 13, 13, 21, 53, 0, UTC)) 
    229230        self.assertEqual(cset.log, "Somehow I mixed up two sentences") 
    230231 
    231232        cset = csets.next() 
    232233        self.assertEqual(cset.author, "rochael") 
    233         self.assertEqual(cset.date, datetime(2004, 8, 13, 13, 59, 55)) 
     234        self.assertEqual(cset.date, datetime(2004, 8, 13, 13, 59, 55, 0, UTC)) 
    234235        self.assertEqual(cset.log, "removed duplicated ENABLE_TEMPLATE_MIXIN") 
    235236        entry = cset.entries[0] 
     
    354355 
    355356        cset = csets[0] 
    356         self.assertEqual(cset.date, datetime(1994, 5, 17, 13, 03, 36)) 
     357        self.assertEqual(cset.date, datetime(1994, 5, 17, 13, 03, 36, 0, UTC)) 
    357358 
    358359        cset = csets[-1] 
    359         self.assertEqual(cset.date, datetime(1995, 12, 30, 18, 32, 46)) 
     360        self.assertEqual(cset.date, datetime(1995, 12, 30, 18, 32, 46, 0, UTC)) 
    360361 
    361362class CvsRevisions(TestCase): 
  • vcpx/tests/darcs.py

    r1208 r1216  
    1111from vcpx.repository.darcs import changesets_from_darcschanges 
    1212from vcpx.shwrap import ExternalCommand, PIPE 
     13from vcpx.tzinfo import UTC 
    1314 
    1415 
     
    5556                         "Fix the CVS parser to omit already seen changesets") 
    5657        self.assertEqual(cset.author, "lele@nautilus.homeip.net") 
    57         self.assertEqual(cset.date, datetime(2004, 7, 16, 12, 37, 37)) 
     58        self.assertEqual(cset.date, datetime(2004, 7, 16, 12, 37, 37, 0, UTC)) 
    5859        self.assertEqual(cset.log, "For some unknown reasons....") 
    5960        entry = cset.entries[0] 
     
    6465        self.assertEqual(cset.revision, 
    6566                         "Svn log parser with test") 
    66         self.assertEqual(cset.date, datetime(2004, 6, 1, 14, 5, 59)) 
     67        self.assertEqual(cset.date, datetime(2004, 6, 1, 14, 5, 59, 0, UTC)) 
    6768        self.assertEqual(len(cset.entries), 4) 
    6869        self.assertEqual(cset.darcs_hash, 
     
    213214 
    214215        cset = csets.next() 
    215         self.assertEqual(cset.date, datetime(2003, 10, 14, 9, 42, 0)) 
    216  
    217         cset = csets.next() 
    218         self.assertEqual(cset.date, datetime(2003, 10, 14, 14, 2, 31)) 
     216        self.assertEqual(cset.date, datetime(2003, 10, 14, 9, 42, 0, 0, UTC)) 
     217 
     218        cset = csets.next() 
     219        self.assertEqual(cset.date, datetime(2003, 10, 14, 14, 2, 31, 0, UTC)) 
    219220 
    220221    RENAME_THEN_REMOVE_TEST = """ 
  • vcpx/tests/svn.py

    r1180 r1216  
    99from datetime import datetime 
    1010from vcpx.repository.svn import changesets_from_svnlog 
     11from vcpx.tzinfo import UTC 
    1112 
    1213 
     
    4142        cset = csets.next() 
    4243        self.assertEqual(cset.author, 'lele') 
    43         self.assertEqual(cset.date, datetime(2004,11,12,15,05,37,134366)) 
     44        self.assertEqual(cset.date, datetime(2004,11,12,15,05,37,134366,UTC)) 
    4445        self.assertEqual(cset.log, 'create tree') 
    4546        self.assertEqual(len(cset.entries), 2) 
     
    5556        cset = csets.next() 
    5657        self.assertEqual(cset.author, 'lele') 
    57         self.assertEqual(cset.date, datetime(2004,11,12,15,06,04,193650)) 
     58        self.assertEqual(cset.date, datetime(2004,11,12,15,06,04,193650,UTC)) 
    5859        self.assertEqual(cset.log, 'rename dir') 
    5960        self.assertEqual(len(cset.entries), 1) 
     
    7576        cset = csets.next() 
    7677        self.assertEqual(cset.author, 'anthony') 
    77         self.assertEqual(cset.date, datetime(2004,11,9,6,54,20,709243)) 
     78        self.assertEqual(cset.date, datetime(2004,11,9,6,54,20,709243,UTC)) 
    7879        self.assertEqual(cset.log, 'Moving to a /sandbox') 
    7980        self.assertEqual(len(cset.entries), 1) 
     
    9596        cset = csets[1] 
    9697        self.assertEqual(cset.author, 'lele') 
    97         self.assertEqual(cset.date, datetime(2005,1,8, 17,36,55,174757)) 
     98        self.assertEqual(cset.date, datetime(2005,1,8, 17,36,55,174757,UTC)) 
    9899        self.assertEqual(cset.log, 'Copy') 
    99100        self.assertEqual(len(cset.entries), 1) 
     
    105106 
    106107        cset = csets[2] 
    107         self.assertEqual(cset.date, datetime(2005,1,8, 17,42,41,347315)) 
     108        self.assertEqual(cset.date, datetime(2005,1,8, 17,42,41,347315,UTC)) 
    108109        self.assertEqual(cset.log, 'Remove') 
    109110        self.assertEqual(len(cset.entries), 1) 
     
    114115 
    115116        cset = csets[3] 
    116         self.assertEqual(cset.date, datetime(2005,1,8, 17,43,9,909127)) 
     117        self.assertEqual(cset.date, datetime(2005,1,8, 17,43,9,909127,UTC)) 
    117118        self.assertEqual(cset.log, 'Move') 
    118119        self.assertEqual(len(cset.entries), 1) 
     
    133134        cset = csets.next() 
    134135        self.assertEqual(cset.author, 'cmlenz') 
    135         self.assertEqual(cset.date, datetime(2005,3,21, 8,34, 2,522947)) 
     136        self.assertEqual(cset.date, datetime(2005,3,21, 8,34, 2,522947,UTC)) 
    136137        self.assertEqual(len(cset.entries), 7) 
    137138 
  • vcpx/repository/monotone.py

    r1215 r1216  
    2222from vcpx.target import SynchronizableTargetWorkingDir, TargetInitializationFailure 
    2323from vcpx.changes import Changeset 
     24from vcpx.tzinfo import UTC 
    2425 
    2526 
     
    241242                    y,m,d = map(int, day.split(day[4])) 
    242243                    hh,mm,ss = map(int, time.split(':')) 
    243                     date = datetime(y,m,d,hh,mm,ss) 
     244                    date = datetime(y,m,d,hh,mm,ss,0,UTC) 
    244245                    self.dates.append(date) 
    245246                    state = self.SINGLE 
     
    764765        log.close() 
    765766 
     767        date = date.astimezone(UTC).replace(tzinfo=None) # monotone wants UTC 
    766768        cmd = self.repository.command("commit", 
    767769                                      "--author", encode(author), 
  • vcpx/repository/cdv.py

    r1215 r1216  
    1616from vcpx.target import SynchronizableTargetWorkingDir, TargetInitializationFailure 
    1717from vcpx.source import ChangesetApplicationFailure 
     18from vcpx.tzinfo import UTC 
    1819 
    1920 
     
    8283        cmd = self.repository.command("-u", encode(author), "commit", 
    8384                                      "-m", encode('\n'.join(logmessage)), 
    84                                       "-D", date.strftime('%Y/%m/%d %H:%M:%S UTC')) 
     85                                      "-D", date.astimezone(UTC).strftime('%Y/%m/%d %H:%M:%S UTC')) 
    8586 
    8687        if not entries: 
  • vcpx/repository/tla.py

    r1209 r1216  
    5050                        GetUpstreamChangesetsFailure 
    5151from vcpx.target import TargetInitializationFailure 
     52from vcpx.tzinfo import UTC, FixedOffset 
    5253 
    5354 
     
    206207            if err: 
    207208                raise GetUpstreamChangesetsFailure(str(err)) 
    208             y,m,d,hh,mm,ss,d1,d2,d3 = strptime(msg['Standard-date'], 
    209                                                "%Y-%m-%d %H:%M:%S %Z") 
    210             date = datetime(y,m,d,hh,mm,ss) 
     209 
     210            date = self.__parse_date(msg['Date'], msg['Standard-date']) 
    211211            author = msg['Creator'] 
    212212            revision = fqrev 
     
    221221            changesets.append(Changeset(revision, date, author, logmsg)) 
    222222        return changesets 
     223 
     224    def __parse_date(self, d1, d2): 
     225        # d1: Wed Dec 10 15:01:28 EST 2003 
     226        # d2: 2003-12-10 04:01:28 GMT 
     227 
     228        d1 = datetime(*strptime(d1[:19] + d1[-5:], '%a %b %d %H:%M:%S %Y')[:6]).replace(tzinfo=UTC) 
     229        d2 = datetime(*strptime(d2[:19], '%Y-%m-%d %H:%M:%S')[:6]).replace(tzinfo=UTC) 
     230 
     231        offset = d1 - d2 
     232        offset = offset.seconds + offset.days * 24 * 3600 
     233 
     234        return d1.replace(tzinfo=FixedOffset(offset/60)) 
    223235 
    224236    def __hide_foreign_entries(self): 
  • vcpx/repository/bzr.py

    r1215 r1216  
    113113        from datetime import datetime 
    114114        from vcpx.changes import ChangesetEntry, Changeset 
     115        from vcpx.tzinfo import FixedOffset, UTC 
    115116 
    116117        revision = branch.repository.get_revision(revision_id) 
     
    139140            entries.append(e) 
    140141 
     142        if revision.timezone is not None: 
     143            timezone = FixedOffset(revision.timezone / 60) 
     144        else: 
     145            timezone = UTC 
     146 
    141147        return Changeset(revision.revision_id, 
    142                          datetime.fromtimestamp(revision.timestamp), 
     148                         datetime.fromtimestamp(revision.timestamp, timezone), 
    143149                         revision.committer, 
    144150                         revision.message, 
     
    226232        Commit the changeset. 
    227233        """ 
    228         from time import mktime 
     234        from calendar import timegm  # like mktime(), but returns UTC timestamp 
    229235        from binascii import hexlify 
    230236        from re import search 
     
    242248            self.log.info('Committing...') 
    243249            logmessage = "Empty changelog" 
    244         timestamp = int(mktime(date.timetuple())) 
     250 
     251        timestamp = timegm(date.utctimetuple()) 
     252        timezone  = date.utcoffset().seconds + date.utcoffset().days * 24 * 3600 
    245253 
    246254        # Guess sane email address 
     
    262270                                  specific_files=entries, rev_id=revision_id, 
    263271                                  verbose=self.repository.projectref().verbose, 
    264                                   timestamp=timestamp) 
     272                                  timestamp=timestamp, timezone=timezone) 
    265273 
    266274    def _removePathnames(self, names): 
  • vcpx/repository/cg.py

    r1215 r1216  
    106106            env['GIT_AUTHOR_EMAIL']=email 
    107107        if date: 
    108             env['GIT_AUTHOR_DATE']=str(date) 
     108            env['GIT_AUTHOR_DATE']=date.strftime('%Y-%m-%d %H:%M:%S %z') 
    109109        # '-f' flag means we can get empty commits, which 
    110110        # shouldn't be a problem. 
  • vcpx/repository/git/target.py

    r1215 r1216  
    1818from vcpx.config import ConfigurationError 
    1919from vcpx.target import SynchronizableTargetWorkingDir, TargetInitializationFailure 
     20from vcpx.tzinfo import FixedOffset 
    2021 
    2122from vcpx import TailorException 
     
    2728    "Specified branchpoint not found in parent branch" 
    2829 
     30    ## generic stuff 
     31 
     32class GitRepository(Repository): 
     33    METADIR = '.git' 
     34 
     35    def _load(self, project): 
     36        Repository._load(self, project) 
     37        self.EXECUTABLE = project.config.get(self.name, 'git-command', 'git') 
     38        self.PARENT_REPO = project.config.get(self.name, 'parent-repo') 
     39        self.BRANCHPOINT = project.config.get(self.name, 'branchpoint', 'HEAD') 
     40        self.BRANCHNAME = project.config.get(self.name, 'branch') 
     41        if self.BRANCHNAME: 
     42            self.BRANCHNAME = 'refs/heads/' + self.BRANCHNAME 
     43 
     44        if self.repository and self.PARENT_REPO: 
     45            self.log.critical('Cannot make sense of both "repository" and "parent-repo" parameters') 
     46            raise ConfigurationError ('Must specify only one of "repository" and "parent-repo"') 
     47 
     48        if self.BRANCHNAME and not self.repository: 
     49            self.log.critical('Cannot make sense of "branch" if "repository" is not set') 
     50            raise ConfigurationError ('Missing "repository" to make use o "branch"') 
     51 
     52        self.env = {} 
     53 
     54        if self.repository: 
     55            self.storagedir = self.repository 
     56            self.env['GIT_DIR'] = self.storagedir 
     57            self.env['GIT_INDEX_FILE'] = self.METADIR + '/index' 
     58        else: 
     59            self.storagedir = self.METADIR 
     60 
     61    def _tryCommand(self, cmd, exception=Exception, pipe=True): 
     62        c = GitExternalCommand(self, 
     63                               command = self.command(*cmd), cwd = self.basedir) 
     64        if pipe: 
     65            output = c.execute(stdout=PIPE)[0] 
     66        else: 
     67            c.execute() 
     68        if c.exit_status: 
     69            raise exception(str(c) + ' failed') 
     70        if pipe: 
     71            return output.read().split('\n') 
     72 
     73class GitExternalCommand(ExternalCommand): 
     74    def __init__(self, repo, command=None, cwd=None): 
     75        """ 
     76        Initialize an ExternalCommand instance tied to a GitRepository 
     77        from which it inherits a set of environment variables to use 
     78        for each execute(). 
     79        """ 
     80 
     81        self.repo = repo 
     82        return ExternalCommand.__init__(self, command, cwd) 
     83 
     84    def execute(self, *args, **kwargs): 
     85        """Execute the command, with controlled environment.""" 
     86 
     87        if not kwargs.has_key('env'): 
     88            kwargs['env'] = {} 
     89 
     90        kwargs['env'].update(self.repo.env) 
     91 
     92        return ExternalCommand.execute(self, *args, **kwargs) 
     93 
     94class GitWorkingDir(UpdatableSourceWorkingDir, SynchronizableTargetWorkingDir): 
     95 
     96    ## UpdatableSourceWorkingDir 
     97 
     98    def _checkoutUpstreamRevision(self, revision): 
     99        """ git clone """ 
     100        from os import rename, rmdir 
     101        from os.path import join 
     102 
     103        # Right now we clone the entire repository and just check out to the 
     104        # current rev because it makes revision parsing easier. We can't 
     105        # easily check out arbitrary revisions anyway, but we could probably 
     106        # handle HEAD (master) as a special case... 
     107        # git clone won't checkout into an existing directory 
     108        target = join(self.repository.basedir, '.gittmp') 
     109        # might want -s if we can determine that the path is local. Then again, 
     110        # that makes it a little unsafe to do git write actions here 
     111        self.repository._tryCommand(['clone', '-n', self.repository.repository, target], 
     112                                    ChangesetApplicationFailure, False) 
     113 
     114        rename(join(target, '.git'), join(self.repository.basedir, '.git')) 
     115        rmdir(target) 
     116 
     117        rev = self._getRev(revision) 
     118        if rev != revision: 
     119            self.log.info('Checking out revision %s (%s)' % (rev, revision)) 
     120        else: 
     121            self.log.info('Checking out revision ' + rev) 
     122        self.repository._tryCommand(['reset', '--hard', rev], ChangesetApplicationFailure, False) 
     123 
     124        return self._changesetForRevision(rev) 
     125 
     126    def _getUpstreamChangesets(self, since): 
     127        self.repository._tryCommand(['fetch'], GetUpstreamChangesetsFailure, False) 
     128 
     129        revs = self.repository._tryCommand(['rev-list', '^' + since, 'origin'], 
     130                                           GetUpstreamChangesetsFailure)[:-1] 
     131        revs.reverse() 
     132        for rev in revs: 
     133            self.log.info('Updating to revision ' + rev) 
     134            yield self._changesetForRevision(rev) 
     135 
     136    def _applyChangeset(self, changeset): 
     137        out = self.repository._tryCommand(['merge', '-n', '--no-commit', 'fastforward', 
     138                                           'HEAD', changeset.revision], 
     139                                          ChangesetApplicationFailure) 
     140 
     141        conflicts = [] 
     142        for line in out: 
     143            if line.endswith(': needs update'): 
     144                conflicts.append(line[:-14]) 
     145 
     146        if conflicts: 
     147            self.log.warning("Conflict after 'git merge': %s", ' '.join(conflicts)) 
     148 
     149        return conflicts 
     150 
     151    def _changesetForRevision(self, revision): 
     152        from datetime import datetime 
     153        from vcpx.changes import Changeset, ChangesetEntry 
     154 
     155        action_map = {'A': ChangesetEntry.ADDED, 'D': ChangesetEntry.DELETED, 
     156                      'M': ChangesetEntry.UPDATED, 'R': ChangesetEntry.RENAMED} 
     157 
     158        # find parent 
     159        lines = self.repository._tryCommand(['rev-list', '--pretty=raw', '--max-count=1', revision], 
     160                                            GetUpstreamChangesetsFailure) 
     161        parents = [] 
     162        user = Changeset.ANONYMOUS_USER 
     163        loglines = [] 
     164        date = None 
     165        for line in lines: 
     166            if line.startswith('parent'): 
     167                parents.append(line.split(' ').pop()) 
     168            if line.startswith('author'): 
     169                author_fields = line.split(' ')[1:] 
     170                tz = int(author_fields.pop()) 
     171                dt = int(author_fields.pop()) 
     172                user = ' '.join(author_fields) 
     173                tzsecs = abs(tz) 
     174                tzsecs = (tzsecs / 100 * 60 + tzsecs % 100) * 60 
     175                if tz < 0: 
     176                    tzsecs = -tzsecs 
     177                date = datetime.utcfromtimestamp(dt + tzsecs) 
     178            if line.startswith('    '): 
     179                loglines.append(line.lstrip('    ')) 
     180 
     181        message = '\n'.join(loglines) 
     182        entries = [] 
     183        cmd = ['diff-tree', '--root', '-r', '-M', '--name-status'] 
     184        # haven't thought about merges yet... 
     185        if parents: 
     186            cmd.append(parents[0]) 
     187        cmd.append(revision) 
     188        files = self.repository._tryCommand(cmd, GetUpstreamChangesetsFailure)[:-1] 
     189        if not parents: 
     190            # git lets us know what it's diffing against if we omit parent 
     191            if len(files) > 0: 
     192                files.pop(0) 
     193        for line in files: 
     194            fields = line.split('\t') 
     195            state = fields.pop(0) 
     196            name = fields.pop() 
     197            e = ChangesetEntry(name) 
     198            e.action_kind = action_map[state[0]] 
     199            if e.action_kind == ChangesetEntry.RENAMED: 
     200                e.old_name = fields.pop() 
     201 
     202            entries.append(e) 
     203 
     204        # Brute-force tag search 
     205        from os.path import join 
     206        from os import listdir 
     207 
     208        tags = [] 
     209        tagdir = join(self.repository.basedir, '.git', 'refs', 'tags') 
     210        try: 
     211            for tag in listdir(tagdir): 
     212                # Consider caching stat info per tailor run 
     213                tagrev = self.repository._tryCommand(['rev-list', '--max-count=1', tag])[0] 
     214                if (tagrev == revision): 
     215                    tags.append(tag) 
     216        except OSError: 
     217            # No tag dir 
     218            pass 
     219 
     220        return Changeset(revision, date, user, message, entries, tags=tags) 
     221 
     222    def _getRev(self, revision): 
     223        """ Return the git object corresponding to the symbolic revision """ 
     224        if revision == 'INITIAL': 
     225            return self.repository._tryCommand(['rev-list', 'HEAD'], GetUpstreamChangesetsFailure)[-2] 
     226 
     227        return self.repository._tryCommand(['rev-parse', '--verify', revision], GetUpstreamChangesetsFailure)[0] 
    29228 
    30229class GitTargetWorkingDir(SynchronizableTargetWorkingDir): 
     
    117316            env['GIT_COMMITTER_EMAIL']=email 
    118317        if date: 
    119             env['GIT_AUTHOR_DATE']=date.strftime("%Y-%m-%d %H:%M:%S") 
     318            env['GIT_AUTHOR_DATE']=date.strftime("%Y-%m-%d %H:%M:%S %z") 
    120319            env['GIT_COMMITTER_DATE']=env['GIT_AUTHOR_DATE'] 
    121320        if parent: 
  • vcpx/repository/hg.py

    r1209 r1216  
    122122        from datetime import datetime 
    123123        from vcpx.changes import Changeset, ChangesetEntry 
     124        from vcpx.tzinfo import FixedOffset 
    124125 
    125126        entries = [] 
     
    128129        (manifest, user, date, files, message) = repo.changelog.read(node) 
    129130 
    130         # Different targets seem to handle the TZ differently. It looks like 
    131         # darcs may be the most correct. 
    132131        dt, tz = date 
    133         date = datetime.fromtimestamp(int(dt) + int(tz)) 
     132        date = datetime.fromtimestamp(dt, FixedOffset(-tz/60)) # note the minus sign! 
    134133 
    135134        manifest = repo.manifest.read(manifest) 
     
    252251 
    253252    def _commit(self, date, author, patchname, changelog=None, names=[]): 
    254         from time import mktime 
     253        from calendar import timegm  # like mktime(), but returns UTC timestamp 
    255254 
    256255        encode = self.repository.encode 
     
    267266            self.log.info('Committing...') 
    268267            logmessage = "Empty changelog" 
     268 
     269        timestamp = timegm(date.utctimetuple()) 
     270        timezone  = date.utcoffset().seconds + date.utcoffset().days * 24 * 3600 
     271 
    269272        opts = {} 
    270273        opts['message'] = logmessage 
    271274        opts['user'] = encode(author) 
    272         opts['date'] =  '%d 0' % mktime(date.timetuple()) 
     275        opts['date'] =  '%d %d' % (timestamp, -timezone) # note the minus sign! 
    273276        self._hgCommand('commit', *[encode(n) for n in names], **opts) 
    274277 
Note: See TracChangeset for help on using the changeset viewer.