This is an autogenerated patch header for a single-debian-patch file. The
delta against upstream is either kept as a single patch, or maintained
in some VCS, and exported as a single patch instead of more manageable
atomic patches.

--- /dev/null
+++ gmailieer-1.3/.github/FUNDING.yml
@@ -0,0 +1,2 @@
+github: gauteh
+custom: ["https://www.paypal.me/gauteh"]
--- /dev/null
+++ gmailieer-1.3/.github/workflows/python-test.yml
@@ -0,0 +1,33 @@
+# This workflow will install Python dependencies, run tests and lint with a single version of Python
+# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions
+
+name: Python tests
+
+on:
+  push:
+    branches: [ master ]
+  pull_request:
+    branches: [ master ]
+
+jobs:
+  build:
+
+    runs-on: ubuntu-latest
+
+    steps:
+    - uses: actions/checkout@v2
+    - name: Set up Python 3.9
+      uses: actions/setup-python@v2
+      with:
+        python-version: 3.9
+    - name: Install notmuch
+      run: |
+        sudo apt-get install notmuch
+    - name: Install dependencies
+      run: |
+        python -m pip install --upgrade pip
+        pip install pytest
+        if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
+    - name: Test with pytest
+      run: |
+        pytest -v tests
--- gmailieer-1.3.orig/docs/index.md
+++ gmailieer-1.3/docs/index.md
@@ -176,6 +176,14 @@ Lieer can be configured using `gmi set`.
   
   *Important*: See note below on [changing this setting after initial sync](#changing-ignored-tags-and-translation-after-initial-sync).
 
+**`Local Trash Tag (local)`** can be used to set the local tag to which the remote GMail 'TRASH' label is translated.
+
+  *Important*: See note below on [changing this setting after initial sync](#changing-ignored-tags-and-translation-after-initial-sync).
+
+**`Translation List Overlay`** can be used to add or change entries in the translation mapping between local and remote tags. Argument is a comment-separated list with an even number of items. This is interpreted as a list of pairs of (remote, local), where each pair is added to the tag translation overwriting any existing translation for that tag if any. For example,
+`--translation-list-overlay CATEGORY_FORUMS,my_forum_tag` will translate Google's CATEGORY_FORUMS tag to my_forum_tag.')
+
+  *Important*: See note below on [changing this setting after initial sync](#changing-ignored-tags-and-translation-after-initial-sync).
 
 ## Changing ignored tags and translation after initial sync
 
@@ -187,11 +195,12 @@ Before changing either setting make sure
 
 When changing the opposite setting: `--ignore-tags-local`, do a full push (dry-run first): `gmi push -f --dry-run`.
 
-The same goes for the option `--replace-slash-with-dot`. I prefer to do `gmi pull -f --dry-run` after changing this option. This will overwrite the local tags with the remote labels.
+The same goes for the options `--replace-slash-with-dot` and `--local-trash-tag`. I prefer to do `gmi pull -f --dry-run` after changing this option. This will overwrite the local tags with the remote labels.
+
 
 # Translation between labels and tags
 
-We translate some of the GMail labels to other tags. The map of labels to tags are:
+We translate some of the GMail labels to other tags. The default map of labels to tags are:
 
 ```py
   'INBOX'     : 'inbox',
@@ -211,16 +220,22 @@ We translate some of the GMail labels to
   'CATEGORY_FORUMS'       : 'forums',
 ```
 
+The 'trash' local tag can be replaced using the `--local-trash-tag` option.
+
 # Using your own API key
 
 Lieer ships with an API key that is shared openly, this key shares API quota, but [cannot be used to access data](https://github.com/gauteh/lieer/pull/9) unless access is gained to your private `access_token` or `refresh_token`.
 
-You can get an [api key](https://console.developers.google.com/flows/enableapi?apiid=gmail) for a CLI application to use for yourself. Store the `client_secret.json` file somewhere safe and specify it to `gmi auth -c`. You can do this on a repository that is already initialized.
+You can get an [api key](https://console.developers.google.com/flows/enableapi?apiid=gmail) for a CLI application to use for yourself. Store the `client_secret.json` file somewhere safe and specify it to `gmi auth -c`. You can do this on a repository that is already initialized, possibly using `-f` to force reauthorizing with the new client secrets.
 
 
 # Privacy policy
 
-  Lieer downloads e-mail and labels to your local computer. No data is sent elsewhere.
+Lieer downloads e-mail and labels to your local computer. No data is sent elsewhere.
+
+Lieers use and transfer to any other app of information received from Google
+APIs will adhere to [Google API Services User Data Policy](https://developers.google.com/terms/api-services-user-data-policy#additional_requirements_for_specific_api_scopes),
+including the Limited Use requirements
 
 # Caveats
 
--- gmailieer-1.3.orig/lieer/gmailieer.py
+++ gmailieer-1.3/lieer/gmailieer.py
@@ -22,6 +22,7 @@ import  os, sys
 import  argparse
 from    oauth2client import tools
 import  googleapiclient
+import  googleapiclient.errors
 import  notmuch
 
 from .remote import *
@@ -200,6 +201,12 @@ class Gmailieer:
     parser_set.add_argument ('--no-remove-local-messages', action = 'store_true', default = False,
         help = 'Do not remove messages that have been deleted on the remote')
 
+    parser_set.add_argument ('--local-trash-tag', type = str, default = None,
+        help = 'The local tag to use for the remote label TRASH.')
+
+    parser_set.add_argument ('--translation-list-overlay', type = str, default = None,
+        help = 'A list with an even number of items representing a list of pairs of (remote, local), where each pair is added to the tag translation.')
+
     parser_set.set_defaults (func = self.set)
 
 
@@ -306,7 +313,7 @@ class Gmailieer:
 
     # loading local changes
     with notmuch.Database () as db:
-      (rev, uuid) = db.get_revision ()
+      (rev, _) = db.get_revision ()
 
       if rev == self.local.state.lastmod:
         self.vprint ("push: everything is up-to-date.")
@@ -315,8 +322,6 @@ class Gmailieer:
       qry = "path:%s/** and lastmod:%d..%d" % (self.local.nm_relative, self.local.state.lastmod, rev)
 
       query = notmuch.Query (db, qry)
-      total = query.count_messages () # probably destructive here as well
-      query = notmuch.Query (db, qry)
 
       messages = list(query.search_messages ())
       if self.limit is not None and len(messages) > self.limit:
@@ -358,7 +363,7 @@ class Gmailieer:
         self.bar_create (leave = True, total = len(actions), desc = 'pushing, 0 changed')
         changed = 0
 
-        def cb (resp):
+        def cb (_):
           nonlocal changed
           self.bar_update (1)
           changed += 1
@@ -875,6 +880,12 @@ class Gmailieer:
     if args.file_extension is not None:
       self.local.config.set_file_extension (args.file_extension)
 
+    if args.local_trash_tag is not None:
+      self.local.config.set_local_trash_tag (args.local_trash_tag)
+
+    if args.translation_list_overlay is not None:
+      self.local.config.set_translation_list_overlay (args.translation_list_overlay)
+
     print ("Repository information and settings:")
     print ("Account ...........: %s" % self.local.config.account)
     print ("historyId .........: %d" % self.local.state.last_historyId)
@@ -887,6 +898,8 @@ class Gmailieer:
     print ("Replace . with / ..........:", self.local.config.replace_slash_with_dot)
     print ("Ignore tags (local) .......:", self.local.config.ignore_tags)
     print ("Ignore labels (remote) ....:", self.local.config.ignore_remote_labels)
+    print ("Trash tag (local) .........:", self.local.config.local_trash_tag)
+    print ("Translation list overlay ..:", self.local.config.translation_list_overlay)
 
   def vprint (self, *args, **kwargs):
     """
--- gmailieer-1.3.orig/lieer/local.py
+++ gmailieer-1.3/lieer/local.py
@@ -31,7 +31,7 @@ class Local:
 
 
   # NOTE: Update README when changing this map.
-  translate_labels = {
+  translate_labels_default = {
                       'INBOX'     : 'inbox',
                       'SPAM'      : 'spam',
                       'TRASH'     : 'trash',
@@ -49,7 +49,7 @@ class Local:
                       'CATEGORY_FORUMS'       : 'forums',
                       }
 
-  labels_translate = { v: k for k, v in translate_labels.items () }
+  labels_translate_default = { v: k for k, v in translate_labels_default.items () }
 
   ignore_labels = set ([
                         'archive',
@@ -66,6 +66,35 @@ class Local:
                         'voicemail',
                         ])
 
+  def update_translation(self, remote, local):
+    """
+    Convenience function to ensure both maps (remote -> local and local -> remote)
+    get updated when you update a translation.
+    """
+    # Did you reverse the parameters?
+    assert remote in self.translate_labels
+    self.translate_labels[remote] = local
+    self.labels_translate = { v: k for k, v in self.translate_labels.items () }
+
+  def update_translation_list_with_overlay(self, translation_list_overlay):
+    """
+    Takes a list with an even number of items. The list is interpreted as a list of pairs
+    of (remote, local), where each member of each pair is a string. Each pair is added to the
+    translation, overwriting the translation if one already exists (in either direction).
+    If either the remote or the local labels are non-unique, the later items in the list will
+    overwrite the earlier ones in the direction in which the source is non-unique (for example,
+    ["a", "1", "b", 2", "a", "3"] will yield {'a': 3, 'b': 2} in one direction and {1: 'a', 2: 'b', 3: 'a'}
+    in the other).
+    """
+
+    if len(translation_list_overlay) % 2 != 0:
+      raise Exception(f'Translation list overlay must have an even number of items: {translation_list_overlay}')
+
+    for i in range(0,len(translation_list_overlay),2):
+      (remote, local) = translation_list_overlay[i], translation_list_overlay[i+1]
+      self.translate_labels[remote] = local
+      self.labels_translate[local] = remote
+
   class RepositoryException (Exception):
     pass
 
@@ -80,6 +109,8 @@ class Local:
     ignore_remote_labels = None
     remove_local_messages = True
     file_extension = None
+    local_trash_tag = 'trash'
+    translation_list_overlay = None
 
     def __init__ (self, config_f):
       self.config_f = config_f
@@ -103,6 +134,8 @@ class Local:
       self.ignore_tags = set(self.json.get ('ignore_tags', []))
       self.ignore_remote_labels = set(self.json.get ('ignore_remote_labels', Remote.DEFAULT_IGNORE_LABELS))
       self.file_extension = self.json.get ('file_extension', '')
+      self.local_trash_tag = self.json.get ('local_trash_tag', 'trash')
+      self.translation_list_overlay = self.json.get ('translation_list_overlay', [])
 
     def write (self):
       self.json = {}
@@ -116,6 +149,8 @@ class Local:
       self.json['ignore_remote_labels'] = list(self.ignore_remote_labels)
       self.json['remove_local_messages'] = self.remove_local_messages
       self.json['file_extension'] = self.file_extension
+      self.json['local_trash_tag'] = self.local_trash_tag
+      self.json['translation_list_overlay'] = self.translation_list_overlay
 
       if os.path.exists (self.config_f):
         shutil.copyfile (self.config_f, self.config_f + '.bak')
@@ -166,7 +201,7 @@ class Local:
 
     def set_file_extension (self, t):
       try:
-        with tempfile.NamedTemporaryFile (dir = os.path.dirname (self.state_f), suffix = t) as fd:
+        with tempfile.NamedTemporaryFile (dir = os.path.dirname (self.config_f), suffix = t) as _:
           pass
 
         self.file_extension = t.strip ()
@@ -175,6 +210,23 @@ class Local:
         print ("Failed creating test file with file extension: " + t + ", not set.")
         raise
 
+    def set_local_trash_tag (self, t):
+      if ',' in t:
+        print('The local_trash_tag must be a single tag, not a list.  Commas are not allowed.')
+        raise ValueError()
+      self.local_trash_tag = t.strip() or 'trash'
+      self.write()
+
+    def set_translation_list_overlay (self, t):
+      if len(t.strip ()) == 0:
+        self.translation_list_overlay = []
+      else:
+        self.translation_list_overlay = [ tt.strip () for tt in t.split(',') ]
+      if len(self.translation_list_overlay) % 2 != 0:
+        raise Exception(f'Translation list overlay must have an even number of items: {self.translation_list_overlay}')
+      self.write ()
+
+
 
   class State:
     # last historyid of last synchronized message, anything that has happened
@@ -241,7 +293,7 @@ class Local:
       self.lastmod = m
       self.write ()
 
-
+  # we are in the class "Local"; this is the Local instance constructor
   def __init__ (self, g):
     self.gmailieer = g
     self.wd = os.getcwd ()
@@ -255,6 +307,10 @@ class Local:
     # mail store
     self.md = os.path.join (self.wd, 'mail')
 
+    # initialize label translation instance variables
+    self.translate_labels = Local.translate_labels_default.copy()
+    self.labels_translate = Local.labels_translate_default.copy()
+
   def load_repository (self, block = False):
     """
     Loads the current local repository
@@ -269,11 +325,6 @@ class Local:
              for mail_dir in ('cur', 'new', 'tmp')]):
       raise Local.RepositoryException ('local repository not initialized: could not find mail dir structure')
 
-    self.config = Local.Config (self.config_f)
-    self.state = Local.State (self.state_f, self.config)
-
-    self.ignore_labels = self.ignore_labels | self.config.ignore_tags
-
     ## Check if we are in the notmuch db
     with notmuch.Database () as db:
       try:
@@ -299,6 +350,13 @@ class Local:
     except OSError:
       raise Local.RepositoryException ("failed to lock repository (probably in use by another gmi instance)")
 
+    self.config = Local.Config (self.config_f)
+    self.state = Local.State (self.state_f, self.config)
+
+    self.ignore_labels = self.ignore_labels | self.config.ignore_tags
+    self.update_translation('TRASH', self.config.local_trash_tag)
+    self.update_translation_list_with_overlay(self.config.translation_list_overlay)
+
     self.__load_cache__ ()
 
     # load notmuch config
@@ -319,12 +377,12 @@ class Local:
     ## this cache is used to know which messages we have a physical copy of.
     ## hopefully this won't grow too gigantic with lots of messages.
     self.files = []
-    for (dp, dirnames, fnames) in os.walk (os.path.join (self.md, 'cur')):
+    for (_, _, fnames) in os.walk (os.path.join (self.md, 'cur')):
       _fnames = ( 'cur/' + f for f in fnames )
       self.files.extend (_fnames)
       break
 
-    for (dp, dirnames, fnames) in os.walk (os.path.join (self.md, 'new')):
+    for (_, _, fnames) in os.walk (os.path.join (self.md, 'new')):
       _fnames = ( 'new/' + f for f in fnames )
       self.files.extend (_fnames)
       break
@@ -507,7 +565,7 @@ class Local:
       raise Local.RepositoryException ("local file already exists: %s" % p)
 
     if os.path.exists (tmp_p):
-      raise Local.RepositoryException ("local file already exists: %s" % p)
+      raise Local.RepositoryException ("local temporary file already exists: %s" % tmp_p)
 
     if not self.dry_run:
       with open (tmp_p, 'wb') as fd:
@@ -582,9 +640,9 @@ class Local:
       else:
         try:
           if hasattr (notmuch.Database, 'index_file'):
-            (nmsg, stat) = db.index_file (fname, True)
+            (nmsg, _) = db.index_file (fname, True)
           else:
-            (nmsg, stat) = db.add_message (fname, True)
+            (nmsg, _) = db.add_message (fname, True)
         except notmuch.errors.FileNotEmailError:
           print('%s is not an email' % fname)
           return True
--- gmailieer-1.3.orig/lieer/remote.py
+++ gmailieer-1.3/lieer/remote.py
@@ -333,16 +333,16 @@ class Remote:
       try:
         batch.execute (http = self.http)
 
-        # gradually reduce user delay if we had 10 ok batches
+        # gradually reduce user delay upon every ok batch
         user_rate_ok += 1
-        if user_rate_delay > 0 and user_rate_ok > 10:
+        if user_rate_delay > 0 and user_rate_ok > 0:
           user_rate_delay = user_rate_delay // 2
           print ("remote: decreasing delay to %s" % user_rate_delay)
           user_rate_ok    = 0
 
-        # gradually increase batch request size if we had 10 ok requests
+        # gradually increase batch request size upon every ok request
         req_ok += 1
-        if max_req < self.BATCH_REQUEST_SIZE and req_ok > 10:
+        if max_req < self.BATCH_REQUEST_SIZE and req_ok > 0:
           max_req = min (max_req * 2, self.BATCH_REQUEST_SIZE)
           print ("remote: increasing batch request size to: %d" % max_req)
           req_ok  = 0
@@ -606,7 +606,7 @@ class Remote:
     Push label changes
     """
     max_req = self.BATCH_REQUEST_SIZE
-    N       = len (actions)
+    N       = len(actions)
     i       = 0
     j       = 0
 
@@ -615,19 +615,19 @@ class Remote:
     # How many requests with the current delay returned ok.
     user_rate_ok        = 0
 
-    def _cb (rid, resp, excep):
+    def _cb(rid, resp, excep):
       nonlocal j
       if excep is not None:
         if type(excep) is googleapiclient.errors.HttpError and excep.resp.status == 404:
           # message could not be found this is probably a deleted message, spam or draft
           # message since these are not included in the messages.get() query by default.
-          print ("remote: could not find remote message: %s!" % gids[j])
+          print ("remote: could not find remote message: %s!" % resp)
           j += 1
           return
 
         elif type(excep) is googleapiclient.errors.HttpError and excep.resp.status == 400:
           # message id invalid, probably caused by stray files in the mail repo
-          print ("remote: message id: %s is invalid! are there any non-lieer files created in the lieer repository?" % gids[j])
+          print ("remote: message id is invalid! are there any non-lieer files created in the lieer repository? %s" % resp)
           j += 1
           return
 
@@ -639,16 +639,16 @@ class Remote:
       else:
         j += 1
 
-      cb (resp)
+      cb(resp)
 
     while i < N:
       n = 0
       j = i
-      batch = self.service.new_batch_http_request  (callback = _cb)
+      batch = self.service.new_batch_http_request(callback = _cb)
 
       while n < max_req and i < N:
         a = actions[i]
-        batch.add (a)
+        batch.add(a)
         n += 1
         i += 1
 
@@ -666,14 +666,14 @@ class Remote:
           user_rate_delay = user_rate_delay // 2
           user_rate_ok    = 0
 
-      except Remote.UserRateException as ex:
+      except Remote.UserRateException:
         user_rate_delay = user_rate_delay * 2 + 1
         print ("remote: user rate error, increasing delay to %s" % user_rate_delay)
         user_rate_ok = 0
 
         i = j # reset
 
-      except Remote.BatchException as ex:
+      except Remote.BatchException:
         if max_req > self.MIN_BATCH_REQUEST_SIZE:
           max_req = max_req / 2
           i = j # reset
--- /dev/null
+++ gmailieer-1.3/tests/__init__.py
@@ -0,0 +1,18 @@
+import pytest
+import tempfile
+
+class MockGmi:
+    dry_run = False
+
+    def __init__(self):
+        pass
+
+
+
+@pytest.fixture
+def gmi():
+    """
+    Test gmi
+    """
+
+    return MockGmi()
--- /dev/null
+++ gmailieer-1.3/tests/test_local.py
@@ -0,0 +1,14 @@
+from . import *
+import lieer
+
+
+def test_update_translation_list(gmi):
+    l = lieer.Local(gmi)
+    l.update_translation_list_with_overlay(['a', '1', 'b', '2'])
+    assert l.translate_labels['a'] == '1'
+    assert l.translate_labels['b'] == '2'
+    assert l.labels_translate['1'] == 'a'
+    assert l.labels_translate['2'] == 'b'
+
+    with pytest.raises(Exception):
+        l.update_translation_list_with_overlay(['a', '1', 'b', '2', 'c'])
