Merge lp:~fgallaire/bzr/fix-gmtime-lite into lp:bzr

Proposed by Florent Gallaire
Status: Merged
Approved by: Vincent Ladeuil
Approved revision: 6622
Merged at revision: 6622
Proposed branch: lp:~fgallaire/bzr/fix-gmtime-lite
Merge into: lp:bzr
Diff against target: 228 lines (+60/-18)
9 files modified
bzrlib/annotate.py (+1/-1)
bzrlib/crash.py (+1/-1)
bzrlib/doc_generate/autodoc_bash_completion.py (+2/-2)
bzrlib/doc_generate/autodoc_man.py (+2/-2)
bzrlib/doc_generate/autodoc_rstx.py (+1/-2)
bzrlib/osutils.py (+15/-4)
bzrlib/tests/blackbox/test_commit.py (+34/-1)
bzrlib/timestamp.py (+1/-5)
doc/en/release-notes/bzr-2.8.txt (+3/-0)
To merge this branch: bzr merge lp:~fgallaire/bzr/fix-gmtime-lite
Reviewer Review Type Date Requested Status
Vincent Ladeuil Approve
Richard Wilbur Approve
Review via email: mp+318653@code.launchpad.net

Commit message

Fix for Windows and 32-bit platforms buggy gmtime().

Description of the change

Fix for Windows buggy gmtime()
Fix for 32-bit platforms gmtime()
Light implementation

To post a comment you must log in.
Revision history for this message
Richard Wilbur (richard-wilbur) wrote :

Have you filed a bug report for this problem? Or if it is already filed as a bug, let's link it here.

review: Needs Information
Revision history for this message
Florent Gallaire (fgallaire) wrote :

No open bug, it is necessary to file one ?

Revision history for this message
Richard Wilbur (richard-wilbur) wrote :

We like to keep track of defects using bugs: steps to reproduce help developers diagnose the issue and also write good tests. Since we use Test-Driven Development we prefer to demonstrate an issue with a failing test case. Then a good fix solves the failing test without making any others fail.

Revision history for this message
Richard Wilbur (richard-wilbur) wrote :

Thank you for coding up a solution.

Did you notice this as a bug in the operation of bzr or are you fixing the issue mentioned in the code?

If you would like assistance filing a bug report or creating a test case, please let me know.

Revision history for this message
Florent Gallaire (fgallaire) wrote :

Richard, I didn't notice this in the operation of bzr as I don't use Windows, but you can easily test it.

This is the bug mentioned in the code, I have already fixed it in Mercurial:
https://www.mercurial-scm.org/repo/hg/rev/87c6ad2251d8

Revision history for this message
Florent Gallaire (fgallaire) wrote :
Revision history for this message
Florent Gallaire (fgallaire) wrote :

Very unhappy with your Test-Driven Development practice because I was trying to make code cleaner and found that:
http://bazaar.launchpad.net/~bzr-pqm/bzr/bzr.dev/revision/3754

Revision history for this message
Vincent Ladeuil (vila) wrote :

> but you can easily test it.

No we can't (at least I and Richard can't).
That's the issue here, without testing on windows, nothing can really be fixed.

> Very unhappy with your Test-Driven Development practice because I was trying to make code cleaner and found that:

What is making you unhappy ?

Revision history for this message
Florent Gallaire (fgallaire) wrote :

> > but you can easily test it.
>
> No we can't (at least I and Richard can't).
> That's the issue here, without testing on windows, nothing can really be
> fixed.

I have set up a Windows VM, you can see a screenshot of the bug in the report I filed.

> > Very unhappy with your Test-Driven Development practice because I was trying
> to make code cleaner and found that:
>
> What is making you unhappy ?

Look at the commit: awful replicated and spaghetti code justified by an useless test.

Revision history for this message
Vincent Ladeuil (vila) wrote :

> > > but you can easily test it.
> >
> > No we can't (at least I and Richard can't).
> > That's the issue here, without testing on windows, nothing can really be
> > fixed.
>
> I have set up a Windows VM, you can see a screenshot of the bug in the report
> I filed.

Ha, that explains it, I receive bug reports by mail and rarely looked at attachment.

Copy/pasting the text was not an option ? It would make citing your bug report easier rather than having to type what is in the screenshot:

> bzr 2.6b1 python 2.6.6 (Windows-7-6.1.7600)

Neither bzr-2.6 nor python2.6 are supported anymore.

> > > Very unhappy with your Test-Driven Development practice because I was
> trying
> > to make code cleaner and found that:
> >
> > What is making you unhappy ?
>
> Look at the commit: awful replicated and spaghetti code justified by an
> useless test.

Oook.

So, I didn't write that code nor that test so I won't take that personally.

Now, if you expect any project contributor to welcome your own contribution, it may be a good idea to not insult their work to start with.

Since you have a way to reproduce the issue though, and know how to TDD better than us, we'll welcome a a test reproducing that issue with the current code base (as mentioned above 2.6b1 has EOL'ed long ago) and a fix making the test pass.

Revision history for this message
Florent Gallaire (fgallaire) wrote :

> > bzr 2.6b1 python 2.6.6 (Windows-7-6.1.7600)
>
> Neither bzr-2.6 nor python2.6 are supported anymore.

I have installed the last version packaged for Windows to show you the bug:
http://wiki.bazaar.canonical.com/WindowsDownloads

So in fact, is bazaar still supporting the Windows platform ?

> So, I didn't write that code nor that test so I won't take that personally.
>
> Now, if you expect any project contributor to welcome your own contribution,
> it may be a good idea to not insult their work to start with.

No offence, and no insult here. The author of the commit himself has commented that this was replicated code and for the only reason to make the test working... anyway it isn't the point.

Revision history for this message
Vincent Ladeuil (vila) wrote :

> So in fact, is bazaar still supporting the Windows platform ?

See mailing archives for details. At this point the last installer is for 2.6b1 (b for beta).

A contributor willing to build a new installer and as such restore the release pipeline for windows would be highly welcome.

Revision history for this message
Florent Gallaire (fgallaire) wrote :

tests added, ready to merge

Revision history for this message
Vincent Ladeuil (vila) wrote :

Good work thanks !

I'm (pleasantly) surprised that this ends up being so lite (and focused).

As a side effect, it shows that bzr doesn't care about dates (which can't be guaranteed to be accurate in a distributed environment where host clocks can't be trusted).

grepping for 'gmtime(' I found a few references you probably want to fix too:

./bzrlib/doc_generate/autodoc_rstx.py:42: tt = time.gmtime(t)
./bzrlib/doc_generate/autodoc_man.py:48: tt = time.gmtime(t)
./bzrlib/doc_generate/autodoc_bash_completion.py:34: tt = time.gmtime(t)
./bzrlib/crash.py:245: date_string = time.strftime('%Y-%m-%dT%H:%M', time.gmtime())

(I don't think they matter as much but being consistent and using osutils.gmtime() instead of time.gmtime() remove confusion about why the code base would use two different versions).

This bug fix is worth mentioning in the news in doc/en/release-notes/bzr-2.8.txt

Final administrativia: please sign the CLA https://www.ubuntu.com/legal/contributors

review: Needs Fixing
Revision history for this message
Florent Gallaire (fgallaire) wrote :

> Good work thanks !

Thanks

> ./bzrlib/doc_generate/autodoc_rstx.py:42: tt = time.gmtime(t)
> ./bzrlib/doc_generate/autodoc_man.py:48: tt = time.gmtime(t)
> ./bzrlib/doc_generate/autodoc_bash_completion.py:34: tt = time.gmtime(t)
> ./bzrlib/crash.py:245: date_string = time.strftime('%Y-%m-%dT%H:%M',
> time.gmtime())
>
> (I don't think they matter as much but being consistent and using
> osutils.gmtime() instead of time.gmtime() remove confusion about why the code
> base would use two different versions).

I haven't replaced time.gmtime() and time.gmtime(time.time()) because they run against the present time which is not buggy, will only be if we still use bazaar on 32-bit platforms in more than 20 years.

Do you want I fix it even so ?

> Final administrativia: please sign the CLA
> https://www.ubuntu.com/legal/contributors

Done

Revision history for this message
Richard Wilbur (richard-wilbur) wrote :

@Florent: Thank you for filing the bugs and creating the tests.

Since these problems have our attention presently, go ahead and fix the 4 files still using time.gmtime to use your new implementation in osutils.gmtime. Then we will be consistently using osutils.gmtime and won't have this particular issue in 20+ years should we still have users running bzr on 32-bit platforms at that time. (fairly likely)

Revision history for this message
Vincent Ladeuil (vila) wrote :

In addition to what Richard said, having a single call makes it easier for newcomers to use the right one without having to research which one is appropriate for their case.

By the way, adding a docstring to osutils.gmtime() seems like the right way to achieve that. Nothing exotic, just mentioning why using time.gmtime() is not appropriate and why using osutils.gmtime() is better.

Revision history for this message
Florent Gallaire (fgallaire) wrote :

Should be good now.

Revision history for this message
Richard Wilbur (richard-wilbur) wrote :

Thank you for the fix to some problems I didn't know we had. Thank you also for the explanatory docstring and updating the rest of the code to use the fix.

I have no further reservations about merging this.

+1

@Vincent: Should we target 7.1 and then merge up to trunk?

review: Approve
Revision history for this message
Vincent Ladeuil (vila) wrote :

Thanks !

> Should we target 7.1 and then merge up to trunk?

No, it's not a critical issue and almost a new feature, so... trunk is appropriate.

Will land asap.

review: Approve
Revision history for this message
Florent Gallaire (fgallaire) wrote :

Thanks @Richard and @Vincent, happy to work with you.

Revision history for this message
bzr PQM (bzr-pqm) wrote :
Download full text (16.6 KiB)

The attempt to merge lp:~fgallaire/bzr/fix-gmtime-lite into lp:bzr failed. Below is the output from the failed tests.

python tools/rst2html.py --link-stylesheet --footnote-references=superscript --halt=warning --stylesheet=../../default.css doc/en/tutorials/tutorial.txt "doc/en/tutorials/tutorial.html"
python tools/rst2html.py --link-stylesheet --footnote-references=superscript --halt=warning --stylesheet=../../default.css doc/en/tutorials/using_bazaar_with_launchpad.txt "doc/en/tutorials/using_bazaar_with_launchpad.html"
python tools/rst2html.py --link-stylesheet --footnote-references=superscript --halt=warning --stylesheet=../../default.css doc/en/tutorials/centralized_workflow.txt "doc/en/tutorials/centralized_workflow.html"
python tools/rst2html.py --link-stylesheet --footnote-references=superscript --halt=warning --stylesheet=../../default.css doc/ru/tutorials/centralized_workflow.txt "doc/ru/tutorials/centralized_workflow.html"
python tools/rst2html.py --link-stylesheet --footnote-references=superscript --halt=warning --stylesheet=../../default.css doc/ru/tutorials/using_bazaar_with_launchpad.txt "doc/ru/tutorials/using_bazaar_with_launchpad.html"
python tools/rst2html.py --link-stylesheet --footnote-references=superscript --halt=warning --stylesheet=../../default.css doc/ru/tutorials/tutorial.txt "doc/ru/tutorials/tutorial.html"
python tools/rst2html.py --link-stylesheet --footnote-references=superscript --halt=warning --stylesheet=../../default.css doc/ja/tutorials/tutorial.txt "doc/ja/tutorials/tutorial.html"
python tools/rst2html.py --link-stylesheet --footnote-references=superscript --halt=warning --stylesheet=../../default.css doc/ja/tutorials/using_bazaar_with_launchpad.txt "doc/ja/tutorials/using_bazaar_with_launchpad.html"
python tools/rst2html.py --link-stylesheet --footnote-references=superscript --halt=warning --stylesheet=../../default.css doc/ja/tutorials/centralized_workflow.txt "doc/ja/tutorials/centralized_workflow.html"
python tools/rst2html.py --link-stylesheet --footnote-references=superscript --halt=warning --stylesheet=../../default.css doc/en/mini-tutorial/index.txt "doc/en/mini-tutorial/index.html"
python tools/rst2html.py --link-stylesheet --footnote-references=superscript --halt=warning --stylesheet=../../default.css doc/ja/mini-tutorial/index.txt "doc/ja/mini-tutorial/index.html"
python tools/rst2html.py --link-stylesheet --footnote-references=superscript --halt=warning --stylesheet=../../default.css doc/ru/mini-tutorial/index.txt "doc/ru/mini-tutorial/index.html"
python tools/rst2html.py --link-stylesheet --footnote-references=superscript --halt=warning --stylesheet=../../default.css doc/es/mini-tutorial/index.txt "doc/es/mini-tutorial/index.html"
python tools/rst2html.py --link-stylesheet --footnote-references=superscript --halt=warning --stylesheet=../../default.css doc/en/user-guide/index-plain.txt doc/en/user-guide/index-plain.html
python tools/rst2html.py --link-stylesheet --footnote-references=superscript --halt=warning --stylesheet=../../default.css doc/ja/user-guide/index-plain.txt "doc/ja/user-guide/index-plain.html"
python tools/rst2html.py --link-stylesheet --footnote-references=superscript --halt...

lp:~fgallaire/bzr/fix-gmtime-lite updated
6622. By Florent Gallaire

Fix for Windows and 32-bit platforms buggy gmtime().

Revision history for this message
Florent Gallaire (fgallaire) wrote :

Unremoved the needed time import. Tests run fine now.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'bzrlib/annotate.py'
--- bzrlib/annotate.py 2012-06-26 12:14:56 +0000
+++ bzrlib/annotate.py 2017-03-17 10:40:31 +0000
@@ -215,7 +215,7 @@
215 rev = revisions[origin]215 rev = revisions[origin]
216 tz = rev.timezone or 0216 tz = rev.timezone or 0
217 date_str = time.strftime('%Y%m%d',217 date_str = time.strftime('%Y%m%d',
218 time.gmtime(rev.timestamp + tz))218 osutils.gmtime(rev.timestamp + tz))
219 # a lazy way to get something like the email address219 # a lazy way to get something like the email address
220 # TODO: Get real email address220 # TODO: Get real email address
221 author = rev.get_apparent_authors()[0]221 author = rev.get_apparent_authors()[0]
222222
=== modified file 'bzrlib/crash.py'
--- bzrlib/crash.py 2013-01-30 05:55:38 +0000
+++ bzrlib/crash.py 2017-03-17 10:40:31 +0000
@@ -242,7 +242,7 @@
242 # Windows or if it's manually configured it might need to be created,242 # Windows or if it's manually configured it might need to be created,
243 # and then it should be private243 # and then it should be private
244 os.makedirs(crash_dir, mode=0600)244 os.makedirs(crash_dir, mode=0600)
245 date_string = time.strftime('%Y-%m-%dT%H:%M', time.gmtime())245 date_string = time.strftime('%Y-%m-%dT%H:%M', osutils.gmtime())
246 # XXX: getuid doesn't work on win32, but the crash directory is per-user246 # XXX: getuid doesn't work on win32, but the crash directory is per-user
247 if sys.platform == 'win32':247 if sys.platform == 'win32':
248 user_part = ''248 user_part = ''
249249
=== modified file 'bzrlib/doc_generate/autodoc_bash_completion.py'
--- bzrlib/doc_generate/autodoc_bash_completion.py 2011-12-19 13:23:58 +0000
+++ bzrlib/doc_generate/autodoc_bash_completion.py 2017-03-17 10:40:31 +0000
@@ -23,6 +23,7 @@
23import bzrlib23import bzrlib
24import bzrlib.help24import bzrlib.help
25import bzrlib.commands25import bzrlib.commands
26import bzrlib.osutils
2627
2728
28def get_filename(options):29def get_filename(options):
@@ -30,8 +31,7 @@
3031
3132
32def infogen(options, outfile):33def infogen(options, outfile):
33 t = time.time()34 tt = bzrlib.osutils.gmtime()
34 tt = time.gmtime(t)
35 params = \35 params = \
36 { "bzrcmd": options.bzr_name,36 { "bzrcmd": options.bzr_name,
37 "datestamp": time.strftime("%Y-%m-%d",tt),37 "datestamp": time.strftime("%Y-%m-%d",tt),
3838
=== modified file 'bzrlib/doc_generate/autodoc_man.py'
--- bzrlib/doc_generate/autodoc_man.py 2015-03-14 23:44:01 +0000
+++ bzrlib/doc_generate/autodoc_man.py 2017-03-17 10:40:31 +0000
@@ -32,6 +32,7 @@
32import bzrlib.help32import bzrlib.help
33import bzrlib.help_topics33import bzrlib.help_topics
34import bzrlib.commands34import bzrlib.commands
35import bzrlib.osutils
3536
36from bzrlib.plugin import load_plugins37from bzrlib.plugin import load_plugins
37load_plugins()38load_plugins()
@@ -44,8 +45,7 @@
4445
45def infogen(options, outfile):46def infogen(options, outfile):
46 """Assembles a man page"""47 """Assembles a man page"""
47 t = time.time()48 tt = bzrlib.osutils.gmtime()
48 tt = time.gmtime(t)
49 params = \49 params = \
50 { "bzrcmd": options.bzr_name,50 { "bzrcmd": options.bzr_name,
51 "datestamp": time.strftime("%Y-%m-%d",tt),51 "datestamp": time.strftime("%Y-%m-%d",tt),
5252
=== modified file 'bzrlib/doc_generate/autodoc_rstx.py'
--- bzrlib/doc_generate/autodoc_rstx.py 2015-11-15 02:30:05 +0000
+++ bzrlib/doc_generate/autodoc_rstx.py 2017-03-17 10:40:31 +0000
@@ -38,8 +38,7 @@
3838
39def infogen(options, outfile):39def infogen(options, outfile):
40 """Create manual in RSTX format"""40 """Create manual in RSTX format"""
41 t = time.time()41 tt = bzrlib.osutils.gmtime()
42 tt = time.gmtime(t)
43 params = \42 params = \
44 { "bzrcmd": options.bzr_name,43 { "bzrcmd": options.bzr_name,
45 "datestamp": time.strftime("%Y-%m-%d",tt),44 "datestamp": time.strftime("%Y-%m-%d",tt),
4645
=== modified file 'bzrlib/osutils.py'
--- bzrlib/osutils.py 2013-06-24 12:03:12 +0000
+++ bzrlib/osutils.py 2017-03-17 10:40:31 +0000
@@ -27,6 +27,7 @@
27from bzrlib.lazy_import import lazy_import27from bzrlib.lazy_import import lazy_import
28lazy_import(globals(), """28lazy_import(globals(), """
29from datetime import datetime29from datetime import datetime
30from datetime import timedelta
30import getpass31import getpass
31import locale32import locale
32import ntpath33import ntpath
@@ -827,6 +828,16 @@
827 return True828 return True
828829
829830
831def gmtime(seconds=None):
832 """Convert seconds since the Epoch to a time tuple expressing UTC (a.k.a.
833 GMT). When 'seconds' is not passed in, convert the current time instead.
834 Handy replacement for time.gmtime() buggy on Windows and 32-bit platforms.
835 """
836 if seconds is None:
837 seconds = time.time()
838 return (datetime(1970, 1, 1) + timedelta(seconds=seconds)).timetuple()
839
840
830def local_time_offset(t=None):841def local_time_offset(t=None):
831 """Return offset of local zone from GMT, either at present or at time t."""842 """Return offset of local zone from GMT, either at present or at time t."""
832 if t is None:843 if t is None:
@@ -872,7 +883,7 @@
872 """883 """
873 if offset is None:884 if offset is None:
874 offset = 0885 offset = 0
875 tt = time.gmtime(t + offset)886 tt = gmtime(t + offset)
876 date_fmt = _default_format_by_weekday_num[tt[6]]887 date_fmt = _default_format_by_weekday_num[tt[6]]
877 date_str = time.strftime(date_fmt, tt)888 date_str = time.strftime(date_fmt, tt)
878 offset_str = _cache.get(offset, None)889 offset_str = _cache.get(offset, None)
@@ -904,12 +915,12 @@
904915
905def _format_date(t, offset, timezone, date_fmt, show_offset):916def _format_date(t, offset, timezone, date_fmt, show_offset):
906 if timezone == 'utc':917 if timezone == 'utc':
907 tt = time.gmtime(t)918 tt = gmtime(t)
908 offset = 0919 offset = 0
909 elif timezone == 'original':920 elif timezone == 'original':
910 if offset is None:921 if offset is None:
911 offset = 0922 offset = 0
912 tt = time.gmtime(t + offset)923 tt = gmtime(t + offset)
913 elif timezone == 'local':924 elif timezone == 'local':
914 tt = time.localtime(t)925 tt = time.localtime(t)
915 offset = local_time_offset(t)926 offset = local_time_offset(t)
@@ -925,7 +936,7 @@
925936
926937
927def compact_date(when):938def compact_date(when):
928 return time.strftime('%Y%m%d%H%M%S', time.gmtime(when))939 return time.strftime('%Y%m%d%H%M%S', gmtime(when))
929940
930941
931def format_delta(delta):942def format_delta(delta):
932943
=== modified file 'bzrlib/tests/blackbox/test_commit.py'
--- bzrlib/tests/blackbox/test_commit.py 2016-02-01 18:06:32 +0000
+++ bzrlib/tests/blackbox/test_commit.py 2017-03-17 10:40:31 +0000
@@ -713,7 +713,40 @@
713 self.assertEqual(713 self.assertEqual(
714 'Sat 2009-10-10 08:00:00 +0100',714 'Sat 2009-10-10 08:00:00 +0100',
715 osutils.format_date(last_rev.timestamp, last_rev.timezone))715 osutils.format_date(last_rev.timestamp, last_rev.timezone))
716 716
717 def test_commit_time_negative_windows(self):
718 tree = self.make_branch_and_tree('tree')
719 self.build_tree(['tree/hello.txt'])
720 tree.add('hello.txt')
721 out, err = self.run_bzr("commit -m hello "
722 "--commit-time='1969-10-10 00:00:00 +0000' tree/hello.txt")
723 last_rev = tree.branch.repository.get_revision(tree.last_revision())
724 self.assertEqual(
725 'Fri 1969-10-10 00:00:00 +0000',
726 osutils.format_date(last_rev.timestamp, last_rev.timezone))
727
728 def test_commit_time_negative_32bit(self):
729 tree = self.make_branch_and_tree('tree')
730 self.build_tree(['tree/hello.txt'])
731 tree.add('hello.txt')
732 out, err = self.run_bzr("commit -m hello "
733 "--commit-time='1900-01-01 00:00:00 +0000' tree/hello.txt")
734 last_rev = tree.branch.repository.get_revision(tree.last_revision())
735 self.assertEqual(
736 'Mon 1900-01-01 00:00:00 +0000',
737 osutils.format_date(last_rev.timestamp, last_rev.timezone))
738
739 def test_commit_time_positive_32bit(self):
740 tree = self.make_branch_and_tree('tree')
741 self.build_tree(['tree/hello.txt'])
742 tree.add('hello.txt')
743 out, err = self.run_bzr("commit -m hello "
744 "--commit-time='2039-01-01 00:00:00 +0000' tree/hello.txt")
745 last_rev = tree.branch.repository.get_revision(tree.last_revision())
746 self.assertEqual(
747 'Sat 2039-01-01 00:00:00 +0000',
748 osutils.format_date(last_rev.timestamp, last_rev.timezone))
749
717 def test_commit_time_bad_time(self):750 def test_commit_time_bad_time(self):
718 tree = self.make_branch_and_tree('tree')751 tree = self.make_branch_and_tree('tree')
719 self.build_tree(['tree/hello.txt'])752 self.build_tree(['tree/hello.txt'])
720753
=== modified file 'bzrlib/timestamp.py'
--- bzrlib/timestamp.py 2011-12-18 15:28:38 +0000
+++ bzrlib/timestamp.py 2017-03-17 10:40:31 +0000
@@ -57,7 +57,7 @@
57 # revision XML entry will be reproduced faithfully.57 # revision XML entry will be reproduced faithfully.
58 if offset is None:58 if offset is None:
59 offset = 059 offset = 0
60 tt = time.gmtime(t + offset)60 tt = osutils.gmtime(t + offset)
6161
62 return (osutils.weekdays[tt[6]] +62 return (osutils.weekdays[tt[6]] +
63 time.strftime(" %Y-%m-%d %H:%M:%S", tt)63 time.strftime(" %Y-%m-%d %H:%M:%S", tt)
@@ -119,10 +119,6 @@
119 # give the epoch in utc119 # give the epoch in utc
120 if secs == 0:120 if secs == 0:
121 offset = 0121 offset = 0
122 if secs + offset < 0:
123 from warnings import warn
124 warn("gmtime of negative time (%s, %s) may not work on Windows" %
125 (secs, offset))
126 return osutils.format_date(secs, offset=offset,122 return osutils.format_date(secs, offset=offset,
127 date_fmt='%Y-%m-%d %H:%M:%S')123 date_fmt='%Y-%m-%d %H:%M:%S')
128124
129125
=== modified file 'doc/en/release-notes/bzr-2.8.txt'
--- doc/en/release-notes/bzr-2.8.txt 2017-01-17 15:02:46 +0000
+++ doc/en/release-notes/bzr-2.8.txt 2017-03-17 10:40:31 +0000
@@ -36,6 +36,9 @@
36 doc/en/user-reference only contains English documentation.36 doc/en/user-reference only contains English documentation.
37 (Jelmer Vernooij, #1565503)37 (Jelmer Vernooij, #1565503)
3838
39 * Fix for Windows and 32-bit platforms buggy gmtime().
40 (Florent Gallaire, #1669178, #1670243)
41
39Documentation42Documentation
40*************43*************
4144