Repository: getnikola/nikola

Issue:
Infinite rebuild of demo site
If you create a new demo site, it does not finish building: every time you run `nikola build` it does a couple of things. The first part should be fixed by #1746, but besides that there are:

```
Scanning posts....done!
.  render_galleries:output/galleries/demo/index.html
.  sitemap:output/sitemap.xml
```

Now the sitemap is rebuilt since the index.html for the gallery changes, so the main problem is why is the gallery's index.html always updated.

I've tracked this down to the following reason: the `uptodate` dependency of the task contains `context`, which in turn contains with `context['post']` the post belonging to `galleries/demo/index.txt`. Now between the point where doit checks the dependencies and when it writes the hash of the uptodate into its database, the metadata of this post change. Namely, before, it doesn't contain `'annotation'`, but afterwards it contains `'annotation': ''`. (Besides that, there's no difference.)
This causes the hash written to the doit database to always be different from the hash it compares the hash from the database with (when deciding what to rebuild), so this task will _always_ be rebuilt.

(Why this metadata is changed I haven't checked in detail; I guess the problem is that while rendering the templates, `post.meta('annotation')` is queried, which causes an empty value to be written into `post.meta`.)

I'm not sure what's the best way to fix this. One thing which would make sense is to make post's `__repr__` not depend on empty values in the metadata. A possible fix for this would be to change

```
    def __repr__(self):
        # Calculate a hash that represents most data about the post
        m = hashlib.md5()
        # source_path modification date (to avoid reading it)
        m.update(utils.unicode_str(os.stat(self.source_path).st_mtime).encode('utf-8'))
        m.update(utils.unicode_str(json.dumps(self.meta, cls=utils.CustomEncoder, sort_keys=True)).encode('utf-8'))
        return '<Post: {0} {1}>'.format(self.source_path, m.hexdigest())
```

in `nikola/post.py` to

```
...
        m.update(utils.unicode_str(json.dumps({kk: {k: v for k, v in vv.items() if v} for kk, vv in self.meta.items()},
                                   cls=utils.CustomEncoder, sort_keys=True)).encode('utf-8'))
...
```

At least for me, this fixes the problem. What do you think?



Discussion:
(`if v` is a maybe bit too cheap, maybe it should be a more explicit check for empty strings)

`if v` is good, if the value ever becomes "truthy" then it will change, so all is good.

I am 100% ok with that change ... as long as that thing is split into two or three obvious lines instead of a clever one ;-)


Resolving patch:
diff --git a/nikola/post.py b/nikola/post.py
--- a/nikola/post.py
+++ b/nikola/post.py
@@ -235,7 +235,14 @@ def __repr__(self):
         m = hashlib.md5()
         # source_path modification date (to avoid reading it)
         m.update(utils.unicode_str(os.stat(self.source_path).st_mtime).encode('utf-8'))
-        m.update(utils.unicode_str(json.dumps(self.meta, cls=utils.CustomEncoder, sort_keys=True)).encode('utf-8'))
+        clean_meta = {}
+        for k, v in self.meta.items():
+            sub_meta = {}
+            clean_meta[k] = sub_meta
+            for kk, vv in v.items():
+                if vv:
+                    sub_meta[kk] = vv
+        m.update(utils.unicode_str(json.dumps(clean_meta, cls=utils.CustomEncoder, sort_keys=True)).encode('utf-8'))
         return '<Post: {0} {1}>'.format(self.source_path, m.hexdigest())
 
     def _has_pretty_url(self, lang):


Test patch: