diff --git a/README.markdown b/README.markdown
new file mode 100644
--- /dev/null
+++ b/README.markdown
@@ -0,0 +1,6 @@
+zip-archive
+===========
+
+The zip-archive library provides functions for creating, modifying, and
+extracting files from zip archives.
+
diff --git a/changelog b/changelog
new file mode 100644
--- /dev/null
+++ b/changelog
@@ -0,0 +1,41 @@
+zip-archive 0.2.3.2
+
+  * Better normalization of file paths:  "./foo/bar" and "foo/./bar"
+    are now treated the same, for example.  Note that we do not
+    yet treat "foo/../bar" and "bar" as the same.
+
+zip-archive 0.2.3.2
+
+  * Removed lower bound on directory (>= 1.2), which caused build
+    failures with GHC 7.4 and 7.6.
+  * Added travis script for automatic testing on 3 GHC versions.
+
+zip-archive 0.2.3.1
+
+  * Require binary >= 0.7 and directory >= 1.2.  The newer binary
+    is needed to provide toArchiveOrFail.  The other change is
+    mainly for convenience, to avoid lots of ugly conditional
+    compilation.
+
+zip-archive 0.2.3
+
+  * Export new function `toArchiveOrFail`.  Closes #17.
+  * Set general purpose bit flag to use UTF8 in local file header.
+    Otherwise we get a mismatch between the flag in the central
+    directory and the flag in the local file header, which causes some
+    programs not to be able to extract the files.  Closes #19.
+
+zip-archive 0.2.2.1
+
+  * Fix a stack overflow in getWordsTillSig (Tristan Ravitch).
+
+zip-archive 0.2.2
+
+  * Set bit 11 in the file header to ensure other programs
+    recognize UTF-8 encoded file names (Tobias Brandt).
+
+zip-archive 0.2.1
+
+  * Added OptLocation, to specify the path to which a file
+    is to be added when readEntry is used (Stephen McIntosh).
+
diff --git a/src/Codec/Archive/Zip.hs b/src/Codec/Archive/Zip.hs
--- a/src/Codec/Archive/Zip.hs
+++ b/src/Codec/Archive/Zip.hs
@@ -63,7 +63,7 @@
 import Data.Binary
 import Data.Binary.Get
 import Data.Binary.Put
-import Data.List ( nub, find )
+import Data.List ( nub, find, intercalate )
 import Text.Printf
 import System.FilePath
 import System.Directory ( doesDirectoryExist, getDirectoryContents, createDirectoryIfMissing )
@@ -164,13 +164,14 @@
 -- | Deletes an entry from a zip archive.
 deleteEntryFromArchive :: FilePath -> Archive -> Archive
 deleteEntryFromArchive path archive =
-  let path'      = zipifyFilePath path
+  let path'      = normalizePath path
       newEntries = filter (\e -> eRelativePath e /= path') $ zEntries archive
   in  archive { zEntries = newEntries }
 
 -- | Returns Just the zip entry with the specified path, or Nothing.
 findEntryByPath :: FilePath -> Archive -> Maybe Entry
-findEntryByPath path archive = find (\e -> path == eRelativePath e) (zEntries archive)
+findEntryByPath path archive =
+  find (\e -> normalizePath path == eRelativePath e) (zEntries archive)
 
 -- | Returns uncompressed contents of zip entry.
 fromEntry :: Entry -> B.ByteString
@@ -195,7 +196,7 @@
            then (NoCompression, contents, uncompressedSize)
            else (Deflate, compressedData, compressedSize)
       crc32 = CRC32.crc32 contents
-  in  Entry { eRelativePath            = path
+  in  Entry { eRelativePath            = normalizePath path
             , eCompressionMethod       = compressionMethod
             , eLastModified            = modtime
             , eCRC32                   = crc32
@@ -213,7 +214,7 @@
 readEntry opts path = do
   isDir <- doesDirectoryExist path
   -- make sure directories end in / and deal with the OptLocation option
-  let path' = let p = zipifyFilePath $ normalise $ path ++ if isDir then "/" else "" in
+  let path' = let p = normalizePath $ path ++ if isDir then "/" else "" in
               (case [(l,a) | OptLocation l a <- opts] of
                     ((l,a):_) -> if a then l </> p else l
                     _         -> p)
@@ -284,14 +285,14 @@
 -- Internal functions for reading and writing zip binary format.
 
 -- Note that even on Windows, zip files use "/" internally as path separator.
-zipifyFilePath :: FilePath -> String
-zipifyFilePath path =
-  let dir = takeDirectory path
-      fn  = takeFileName path
+normalizePath :: FilePath -> String
+normalizePath path =
+  let dir   = takeDirectory path
+      fn    = takeFileName path
       (_drive, dir') = splitDrive dir
       -- note: some versions of filepath return ["."] if no dir
-      dirParts = dropWhile (==".") $ splitDirectories dir'
-  in  (concat (map (++ "/") dirParts)) ++ fn
+      dirParts = filter (/=".") $ splitDirectories dir'
+  in  intercalate "/" (dirParts ++ [fn])
 
 -- | Uncompress a lazy bytestring.
 compressData :: CompressionMethod -> B.ByteString -> B.ByteString
@@ -470,13 +471,13 @@
 fileHeaderSize :: Entry -> Word32
 fileHeaderSize f =
   fromIntegral $ 4 + 2 + 2 + 2 + 2 + 2 + 2 + 4 + 4 + 4 + 2 + 2 + 2 + 2 + 2 + 4 + 4 +
-    fromIntegral (B.length $ fromString $ zipifyFilePath $ eRelativePath f) +
+    fromIntegral (B.length $ fromString $ normalizePath $ eRelativePath f) +
     B.length (eExtraField f) + B.length (eFileComment f)
 
 localFileSize :: Entry -> Word32
 localFileSize f =
   fromIntegral $ 4 + 2 + 2 + 2 + 2 + 2 + 4 + 4 + 4 + 2 + 2 +
-    fromIntegral (B.length $ fromString $ zipifyFilePath $ eRelativePath f) +
+    fromIntegral (B.length $ fromString $ normalizePath $ eRelativePath f) +
     B.length (eExtraField f) + B.length (eCompressedData f)
 
 -- Local file header:
@@ -568,9 +569,9 @@
   putWord32le $ eCompressedSize f
   putWord32le $ eUncompressedSize f
   putWord16le $ fromIntegral $ B.length $ fromString
-              $ zipifyFilePath $ eRelativePath f
+              $ normalizePath $ eRelativePath f
   putWord16le $ fromIntegral $ B.length $ eExtraField f
-  putLazyByteString $ fromString $ zipifyFilePath $ eRelativePath f
+  putLazyByteString $ fromString $ normalizePath $ eRelativePath f
   putLazyByteString $ eExtraField f
   putLazyByteString $ eCompressedData f
 
@@ -666,14 +667,14 @@
   putWord32le $ eCompressedSize local
   putWord32le $ eUncompressedSize local
   putWord16le $ fromIntegral $ B.length $ fromString
-              $ zipifyFilePath $ eRelativePath local
+              $ normalizePath $ eRelativePath local
   putWord16le $ fromIntegral $ B.length $ eExtraField local
   putWord16le $ fromIntegral $ B.length $ eFileComment local
   putWord16le 0  -- disk number start
   putWord16le $ eInternalFileAttributes local
   putWord32le $ eExternalFileAttributes local
   putWord32le offset
-  putLazyByteString $ fromString $ zipifyFilePath $ eRelativePath local
+  putLazyByteString $ fromString $ normalizePath $ eRelativePath local
   putLazyByteString $ eExtraField local
   putLazyByteString $ eFileComment local
 
diff --git a/zip-archive.cabal b/zip-archive.cabal
--- a/zip-archive.cabal
+++ b/zip-archive.cabal
@@ -1,5 +1,5 @@
 Name:                zip-archive
-Version:             0.2.3.2
+Version:             0.2.3.3
 Cabal-Version:       >= 1.10
 Build-type:          Simple
 Synopsis:            Library for creating and modifying zip archives.
@@ -13,6 +13,7 @@
 Author:              John MacFarlane
 Maintainer:          jgm@berkeley.edu
 Build-Depends:       base
+Extra-Source-Files:  changelog, README.markdown
 
 Source-repository    head
   type:              git
