[Git][debian-gis-team/grass][upstream] New upstream version 8.0.0

Bas Couwenberg (@sebastic) gitlab at salsa.debian.org
Thu Feb 3 18:13:37 GMT 2022



Bas Couwenberg pushed to branch upstream at Debian GIS Project / grass


Commits:
7b128c13 by Bas Couwenberg at 2022-02-03T18:37:27+01:00
New upstream version 8.0.0
- - - - -


13 changed files:

- doc/howto_release.md
- gui/wxpython/gui_core/gselect.py
- gui/wxpython/gui_core/query.py
- gui/wxpython/timeline/frame.py
- gui/wxpython/xml/toolboxes.xml
- gui/wxpython/xml/wxgui_items.xml
- include/VERSION
- lib/db/dbmi_client/start.c
- lib/init/variables.html
- rpm/grass.spec
- scripts/g.extension/g.extension.py
- utils/mkhtml.py
- vector/v.distance/main.c


Changes:

=====================================
doc/howto_release.md
=====================================
@@ -154,9 +154,15 @@ To be done in GH interface:
 
 <https://github.com/OSGeo/grass/releases/new>
 
+- select release_branch first, then
+- fill in "Release Title" (e.g., GRASS GIS 8.0.0RC1)
+- fill in "Create tag" field:
+
 Tag version | target (examples):
   8.0.0RC1  | releasebranch_8_0
 
+- click on "Create new tag: ... on publish"
+
 Add release desciption (re-use existing texts as possible, from
 <https://github.com/OSGeo/grass/releases>)
 
@@ -181,7 +187,13 @@ Using GH API here, see also
 gh api repos/OSGeo/grass/releases/generate-notes -f tag_name="8.0.0" -f previous_tag_name=7.8.6 -f target_commitish=releasebranch_8_0 -q .body
 ```
 
-Importantly, these notes need to be manually sorted into the various categories.
+If this fails, also a date may be used (that of the last release):
+
+```bash
+git log --oneline --after="2021-10-10" | cut -d' ' -f2- | sed 's+^+* +g' | sed 's+(#+https://github.com/OSGeo/grass/pull/+g' | sed 's+)$++g' | sort -u
+```
+
+Importantly, these notes need to be manually sorted into the various categories (modules, wxGUI, library, docker, ...).
 
 ### Changelog file for upload
 
@@ -309,7 +321,7 @@ Software pages:
 
 #### Only in case of new major release
 
-- update cronjob '[cron_grass_HEAD_src_snapshot.sh](https://github.com/OSGeo/grass-addons/tree/master/utils/cronjobs_osgeo_lxd)' on grass.osgeo.org to next
+- update cronjob '[cron_grass8_relbranch_build_binaries.sh](https://github.com/OSGeo/grass-addons/tree/grass8/utils/cronjobs_osgeo_lxd/)' on grass.osgeo.org to next
   but one release tag for the differences
 - wiki updates, only when new major release:
     - {{cmd|xxxx}} macro: <https://grasswiki.osgeo.org/wiki/Template:Cmd>


=====================================
gui/wxpython/gui_core/gselect.py
=====================================
@@ -59,12 +59,6 @@ import wx.lib.filebrowsebutton as filebrowse
 import grass.script as grass
 from grass.script import task as gtask
 from grass.exceptions import CalledModuleError
-from grass.lib.imagery import (
-    I_SIGFILE_TYPE_SIG,
-    I_SIGFILE_TYPE_SIGSET,
-    I_signatures_list_by_type,
-    I_free_signatures_list,
-)
 from grass.pygrass.utils import decode
 
 from gui_core.widgets import ManageSettingsWidget, CoordinatesValidator
@@ -2813,23 +2807,35 @@ class SignatureSelect(wx.ComboBox):
     ):
         super(SignatureSelect, self).__init__(parent, id, size=size, **kwargs)
 
-        sig_type = None
+        items = []
+        if mapsets:
+            for mapset in mapsets:
+                self._append_mapset_signatures(mapset, element, items)
+        else:
+            self._append_mapset_signatures(None, element, items)
+        self.SetItems(items)
+        self.SetValue("")
+
+    def _append_mapset_signatures(self, mapset, element, items):
+        try:
+            from grass.lib.imagery import (
+                I_SIGFILE_TYPE_SIG,
+                I_SIGFILE_TYPE_SIGSET,
+                I_signatures_list_by_type,
+                I_free_signatures_list,
+            )
+        except ImportError as e:
+            sys.stderr.write(
+                _("Unable to import C imagery library functions: %s\n") % e
+            )
+            return
         # Extend here if a new signature type is introduced
         if element == "signatures/sig":
             sig_type = I_SIGFILE_TYPE_SIG
         elif element == "signatures/sigset":
             sig_type = I_SIGFILE_TYPE_SIGSET
-        items = []
-        if sig_type is not None:
-            if mapsets:
-                for mapset in mapsets:
-                    self._append_mapset_signatures(mapset, sig_type, items)
-            else:
-                self._append_mapset_signatures(None, sig_type, items)
-        self.SetItems(items)
-        self.SetValue("")
-
-    def _append_mapset_signatures(self, mapset, sig_type, items):
+        else:
+            return
         list_ptr = ctypes.POINTER(ctypes.c_char_p)
         sig_list = list_ptr()
         count = I_signatures_list_by_type(sig_type, mapset, ctypes.byref(sig_list))


=====================================
gui/wxpython/gui_core/query.py
=====================================
@@ -133,8 +133,9 @@ class QueryDialog(wx.Dialog):
         else:
             label1 = nodes[0].label
             texts.append((_("Copy '%s'" % self._cutLabel(label1)), label1))
-            if nodes[0].data and nodes[0].data[self._colNames[1]]:
-                label2 = nodes[0].data[self._colNames[1]]
+            col1 = self._colNames[1]
+            if nodes[0].data and col1 in nodes[0].data and nodes[0].data[col1]:
+                label2 = nodes[0].data[col1]
                 texts.insert(0, (_("Copy '%s'" % self._cutLabel(label2)), label2))
                 texts.append((_("Copy line"), label1 + ": " + label2))
 


=====================================
gui/wxpython/timeline/frame.py
=====================================
@@ -372,6 +372,7 @@ class TimelineFrame(wx.Frame):
                         facecolors=color,
                         edgecolor="black",
                         alpha=ALPHA,
+                        picker=True,
                     )
                 )
             else:
@@ -382,6 +383,7 @@ class TimelineFrame(wx.Frame):
                         marker="o",
                         linestyle="None",
                         color=color,
+                        picker=True,
                     )[0]
                 )
 


=====================================
gui/wxpython/xml/toolboxes.xml
=====================================
@@ -2023,6 +2023,7 @@
       <wxgui-item name="TplotTool"/>
       <wxgui-item name="TimelineTool"/>
       <wxgui-item name="VDigit"/>
+      <wxgui-item name="RDigit"/>
     </items>
   </toolbox>
 </toolboxes>


=====================================
gui/wxpython/xml/wxgui_items.xml
=====================================
@@ -402,4 +402,10 @@
     <description>Interactive editing and digitization of vector maps.</description>
     <keywords>general,gui,vector,editing,digitizer</keywords>
   </wxgui-item>
+  <wxgui-item name="RDigit">
+    <label>Raster digitizer</label>
+    <command>g.gui.rdigit</command>
+    <description>Interactive editing and digitization of raster maps.</description>
+    <keywords>general,gui,raster,editing,digitizer</keywords>
+  </wxgui-item>
 </wxgui-items>


=====================================
include/VERSION
=====================================
@@ -1,4 +1,4 @@
 8
 0
-0RC2
+0
 2022


=====================================
lib/db/dbmi_client/start.c
=====================================
@@ -94,7 +94,7 @@ dbDriver *db_start_driver(const char *name)
 	return (dbDriver *) NULL;
 
     /* if name is empty use connection.driverName, added by RB 4/2000 */
-    if (name[0] == '\0') {
+    if (name == NULL || name[0] == '\0') {
 	db_get_connection(&connection);
 	if (NULL == (name = connection.driverName))
 	    return (dbDriver *) NULL;


=====================================
lib/init/variables.html
=====================================
@@ -234,7 +234,12 @@ PERMANENT
   <dt>GRASS_PERL</dt>
   <dd>[used during install process for generating man pages]<br>
     set Perl with path.</dd>
-  
+
+  <dt>GRASS_PROXY</dt>
+  <dd>[used during addon install/reinstall process for generating man
+    pages (download commit from GitHub API server and remote modules.xml file)]<br>
+    set the proxy with: <tt>GRASS_PROXY="http=<value>,ftp=<value>"</tt>.</dd>
+
   <dt>GRASS_SKIP_MAPSET_OWNER_CHECK</dt>
   <dd>By default it is not possible to work with MAPSETs that are
     not owned by current user. Setting this variable to any non-empty value


=====================================
rpm/grass.spec
=====================================
@@ -283,9 +283,6 @@ do
 	iconv -f iso8859-1 -t utf8 $man > %{buildroot}%{_mandir}/man1/$(basename $man)"%{name}"
 done
 
-# create symlink to unversioned name
-ln -s %{_bindir}/%{name}%{shortver} %{buildroot}%{_bindir}/%{name}
-
 # symlink docs from GISBASE to standard system location
 mkdir -p %{buildroot}%{_docdir}
 # append shortver to destination since man pages are unversioned
@@ -366,8 +363,11 @@ fi
 %{_libdir}/%{name}%{shortver}/include
 
 %changelog
-* Wed Nov 10 2021 Markus Neteler <neteler at mundialis.de> - 8.0.0-1
-- New upstream version GRASS GIS 8.0.0
+* Sat Jan 15 2022 Markus Neteler <neteler at mundialis.de> - 8.0.0-1
+- New upstream version GRASS GIS 8.0.0RC2
+
+* Thu Nov 11 2021 Sandro Mani <manisandro at gmail.com> - 7.8.6-3
+- Rebuild (gdal)
 
 * Sun Nov 07 2021 Björn Esser <besser82 at fedoraproject.org> - 7.8.6-2
 - Add patch to fix installation path in pkgconfig file
@@ -408,16 +408,16 @@ fi
 * Tue Dec 22 2020 Markus Neteler <neteler at mundialis.de> - 7.8.5-1
 - New upstream version GRASS GIS 7.8.5
 
-* Tue Nov 24 20:59:37 CET 2020 Markus Neteler <neteler at mundialis.de> - 7.8.4-6
+* Tue Nov 24 2020 Markus Neteler <neteler at mundialis.de> - 7.8.4-6
 - Clean up proj-datumgrid requires < f34+
 
-* Fri Nov 20 15:59:37 CET 2020 Sandro Mani <manisandro at gmail.com> - 7.8.4-5
+* Fri Nov 20 2020 Sandro Mani <manisandro at gmail.com> - 7.8.4-5
 - Drop proj-datumgrid requires on f34+
 
-* Fri Nov  6 22:26:41 CET 2020 Sandro Mani <manisandro at gmail.com> - 7.8.4-4
+* Fri Nov  6 2020 Sandro Mani <manisandro at gmail.com> - 7.8.4-4
 - Rebuild (proj, gdal)
 
-* Wed Nov  4 18:22:40 CET 2020 Sandro Mani <manisandro at gmail.com> - 7.8.4-3
+* Wed Nov  4 2020 Sandro Mani <manisandro at gmail.com> - 7.8.4-3
 - Rebuild (PDAL)
 
 * Sat Oct 17 2020 Markus Neteler <neteler at mundialis.de> - 7.8.4-2


=====================================
scripts/g.extension/g.extension.py
=====================================
@@ -354,7 +354,6 @@ def download_addons_paths_file(url, response_format, *args, **kwargs):
                 ),
             )
         return response
-
     except HTTPError as err:
         if err.code == 403 and err.msg == "rate limit exceeded":
             gscript.warning(
@@ -1273,23 +1272,13 @@ def install_extension_xml(edict):
     return None
 
 
-def filter_multi_addon_addons(mlist):
-    """Filter out list of multi-addon addons which contains
-    and installs only *.html manual page, without source/binary
-    excutable module and doesn't need to check metadata.
-
-    e.g. the i.sentinel multi-addon consists of several full i.sentinel.*
-    addons along with a i.sentinel.html overview file.
-
+def get_multi_addon_addons_which_install_only_html_man_page():
+    """Get multi-addon addons which install only manual html page
 
-    :param list mlist: list of multi-addons (groups of addons
-                       with respective addon overview HTML pages)
-
-    :return list mlist: list of individual multi-addons without respective
-                        addon overview HTML pages
+    :return list addons: list of multi-addon addons which install
+                         only manual html page
     """
-    # Make a list of unique addons
-    mlist = list(set(mlist))
+    addons = []
     all_addon_dirs = []
     addon_dirs_with_source_module = []  # *.py, *.c file
     addon_pattern = re.compile(r".*{}".format(options["extension"]))
@@ -1315,12 +1304,31 @@ def filter_multi_addon_addons(mlist):
             # Add all addon dirs
             all_addon_dirs.append(addon["path"])
 
+    for addon in set(all_addon_dirs) ^ set(addon_dirs_with_source_module):
+        addons.append(os.path.basename(addon))
+    return addons
+
+
+def filter_multi_addon_addons(mlist):
+    """Filter out list of multi-addon addons which contains
+    and installs only *.html manual page, without source/binary
+    excutable module and doesn't need to check metadata.
+
+    e.g. the i.sentinel multi-addon consists of several full i.sentinel.*
+    addons along with a i.sentinel.html overview file.
+
+
+    :param list mlist: list of multi-addons (groups of addons
+                       with respective addon overview HTML pages)
+
+    :return list mlist: list of individual multi-addons without respective
+                        addon overview HTML pages
+    """
     # Filters out add-ons that only contain the *.html man page,
     # e.g. multi-addon i.sentinel (root directory) contains only
     # the *.html manual page for installation, it does not need
     # to check if metadata is available if there is no executable module..
-    for subaddon in set(all_addon_dirs) ^ set(addon_dirs_with_source_module):
-        addon = os.path.basename(subaddon)
+    for addon in get_multi_addon_addons_which_install_only_html_man_page():
         if addon in mlist:
             mlist.pop(mlist.index(addon))
     return mlist
@@ -1342,7 +1350,9 @@ def install_module_xml(mlist):
 
     # Filter multi-addon addons
     if len(mlist) > 1:
-        mlist = filter_multi_addon_addons(mlist)
+        mlist = filter_multi_addon_addons(
+            mlist.copy()
+        )  # mlist.copy() keep the original list of add-ons
 
     # update tree
     for name in mlist:
@@ -1422,24 +1432,16 @@ def install_module_xml(mlist):
 def install_extension_win(name):
     """Install extension on MS Windows"""
     grass.message(
-        _("Downloading precompiled GRASS Addons <%s>...") % options["extension"]
+        _("Downloading precompiled GRASS Addons <{}>...").format(options["extension"])
     )
 
     # build base URL
-    if build_platform == "x86_64":
-        platform = build_platform
-    else:
-        platform = "x86"
     base_url = (
         "http://wingrass.fsv.cvut.cz/"
-        "grass%(major)s%(minor)s/%(platform)s/addons/"
-        "grass-%(major)s.%(minor)s.%(patch)s"
-        % {
-            "platform": platform,
-            "major": version[0],
-            "minor": version[1],
-            "patch": version[2],
-        }
+        "grass{major}{minor}/addons/"
+        "grass-{major}.{minor}.{patch}".format(
+            major=version[0], minor=version[1], patch=version[2]
+        )
     )
 
     # resolve ZIP URL
@@ -1794,7 +1796,8 @@ def install_extension_std_platforms(name, source, url, branch):
                             try:
                                 modulename = line.split("=")[1].strip()
                                 if modulename:
-                                    module_list.append(modulename)
+                                    if modulename not in module_list:
+                                        module_list.append(modulename)
                                 else:
                                     grass.fatal(pgm_not_found_message)
                             except IndexError:
@@ -2212,11 +2215,19 @@ def update_manual_page(module):
     # fix logo URL
     pattern = r'''<a href="([^"]+)"><img src="grass_logo.png"'''
     for match in re.finditer(pattern, shtml):
+        if match.group(1)[:4] == "http":
+            continue
         pos.append(match.start(1))
 
     # find URIs
     pattern = r"""<a href="([^"]+)">([^>]+)</a>"""
     addons = get_installed_extensions(force=True)
+    # Multi-addon
+    if len(addons) > 1:
+        for a in get_multi_addon_addons_which_install_only_html_man_page():
+            # Add multi-addon addons which install only manual html page
+            addons.append(a)
+
     for match in re.finditer(pattern, shtml):
         if match.group(1)[:4] == "http":
             continue
@@ -2577,6 +2588,8 @@ def main():
         proxy = urlrequest.ProxyHandler(PROXIES)
         opener = urlrequest.build_opener(proxy)
         urlrequest.install_opener(opener)
+        # Required for mkhtml.py script (get addon git commit from GitHub API server)
+        os.environ["GRASS_PROXY"] = options["proxy"]
 
     # define path
     options["prefix"] = resolve_install_prefix(
@@ -2647,6 +2660,4 @@ if __name__ == "__main__":
     grass_version = grass.version()
     version = grass_version["version"].split(".")
 
-    build_platform = grass_version["build_platform"].split("-", 1)[0]
-
     sys.exit(main())


=====================================
utils/mkhtml.py
=====================================
@@ -16,6 +16,7 @@
 #
 #############################################################################
 
+import http
 import sys
 import os
 import string
@@ -24,6 +25,9 @@ from datetime import datetime
 import locale
 import json
 import pathlib
+import shutil
+import subprocess
+import time
 
 try:
     # Python 2 import
@@ -31,11 +35,26 @@ try:
 except ImportError:
     # Python 3 import
     from html.parser import HTMLParser
+
+from six.moves.urllib import request as urlrequest
+from six.moves.urllib.error import HTTPError, URLError
+
 try:
     import urlparse
 except ImportError:
     import urllib.parse as urlparse
 
+try:
+    import grass.script as gs
+except ImportError:
+    # During compilation GRASS GIS
+    gs = None
+
+HEADERS = {
+    "User-Agent": "Mozilla/5.0",
+}
+HTTP_STATUS_CODES = list(http.HTTPStatus)
+
 if sys.version_info[0] == 2:
     PY2 = True
 else:
@@ -46,6 +65,24 @@ if not PY2:
     unicode = str
 
 
+grass_version = os.getenv("VERSION_NUMBER", "unknown")
+trunk_url = ""
+addons_url = ""
+if grass_version != "unknown":
+    major, minor, patch = grass_version.split(".")
+    grass_git_branch = "releasebranch_{major}_{minor}".format(
+        major=major,
+        minor=minor,
+    )
+    base_url = "https://github.com/OSGeo"
+    trunk_url = "{base_url}/grass/tree/{branch}/".format(
+        base_url=base_url, branch=grass_git_branch
+    )
+    addons_url = "{base_url}/grass-addons/tree/grass{major}/".format(
+        base_url=base_url, major=major
+    )
+
+
 def _get_encoding():
     encoding = locale.getdefaultlocale()[1]
     if not encoding:
@@ -68,6 +105,151 @@ def decode(bytes_):
     return unicode(bytes_)
 
 
+def urlopen(url, *args, **kwargs):
+    """Wrapper around urlopen. Same function as 'urlopen', but with the
+    ability to define headers.
+    """
+    request = urlrequest.Request(url, headers=HEADERS)
+    return urlrequest.urlopen(request, *args, **kwargs)
+
+
+def set_proxy():
+    """Set proxy"""
+    proxy = os.getenv("GRASS_PROXY")
+    if proxy:
+        proxies = {}
+        for ptype, purl in (p.split("=") for p in proxy.split(",")):
+            proxies[ptype] = purl
+        urlrequest.install_opener(
+            urlrequest.build_opener(urlrequest.ProxyHandler(proxies))
+        )
+
+
+set_proxy()
+
+
+def download_git_commit(url, response_format, *args, **kwargs):
+    """Download module/addon last commit from GitHub API
+
+    :param str url: url address
+    :param str response_format: content type
+
+    :return urllib.request.urlopen or None response: response object or
+                                                     None
+    """
+    try:
+        response = urlopen(url, *args, **kwargs)
+        if not response.code == 200:
+            index = HTTP_STATUS_CODES.index(response.code)
+            desc = HTTP_STATUS_CODES[index].description
+            gs.fatal(
+                _(
+                    "Download commit from <{url}>, return status code "
+                    "{code}, {desc}".format(
+                        url=url,
+                        code=response.code,
+                        desc=desc,
+                    ),
+                ),
+            )
+        if response_format not in response.getheader("Content-Type"):
+            gs.fatal(
+                _(
+                    "Wrong downloaded commit file format. "
+                    "Check url <{url}>. Allowed file format is "
+                    "{response_format}.".format(
+                        url=url,
+                        response_format=response_format,
+                    ),
+                ),
+            )
+        return response
+    except HTTPError as err:
+        gs.warning(
+            _(
+                "The download of the commit from the GitHub API "
+                "server wasn't successful, <{}>. Commit and commit "
+                "date will not be included in the <{}> addon html manual "
+                "page.".format(err.msg, pgm)
+            ),
+        )
+    except URLError:
+        gs.warning(
+            _(
+                "Download file from <{url}>, failed. Check internet "
+                "connection. Commit and commit date will not be included "
+                "in the <{pgm}> addon manual page.".format(url=url, pgm=pgm)
+            ),
+        )
+
+
+def get_last_git_commit(src_dir, is_addon, addon_path):
+    """Get last module/addon git commit
+
+    :param str src_dir: module/addon source dir
+    :param bool is_addon: True if it is addon
+    :param str addon_path: addon path
+
+    :return dict git_log: dict with key commit and date, if not
+                          possible download commit from GitHub API server
+                          values of keys have "unknown" string
+    """
+    unknown = "unknown"
+    git_log = {"commit": unknown, "date": unknown}
+    datetime_format = "%A %b %d %H:%M:%S %Y"  # e.g. Sun Jan 16 23:09:35 2022
+    if is_addon:
+        grass_addons_url = (
+            "https://api.github.com/repos/osgeo/grass-addons/commits?path={path}"
+            "&page=1&per_page=1&sha=grass{major}".format(
+                path=addon_path,
+                major=major,
+            )
+        )  # sha=git_branch_name
+    else:
+        core_module_path = os.path.join(
+            *(set(src_dir.split(os.path.sep)) ^ set(topdir.split(os.path.sep)))
+        )
+        grass_modules_url = (
+            "https://api.github.com/repos/osgeo/grass/commits?path={path}"
+            "&page=1&per_page=1&sha={branch}".format(
+                branch=grass_git_branch,
+                path=core_module_path,
+            )
+        )  # sha=git_branch_name
+
+    if shutil.which("git"):
+        if os.path.exists(src_dir):
+            git_log["date"] = time.ctime(os.path.getmtime(src_dir))
+        stdout, stderr = subprocess.Popen(
+            args=["git", "log", "-1", src_dir],
+            stdout=subprocess.PIPE,
+            stderr=subprocess.PIPE,
+        ).communicate()
+        stdout = decode(stdout)
+        stderr = decode(stderr)
+
+        if stderr and "fatal: not a git repository" in stderr:
+            response = download_git_commit(
+                url=grass_addons_url if is_addon else grass_modules_url,
+                response_format="application/json",
+            )
+            if response:
+                commit = json.loads(response.read())
+                if commit:
+                    git_log["commit"] = commit[0]["sha"]
+                    git_log["date"] = datetime.strptime(
+                        commit[0]["commit"]["author"]["date"],
+                        "%Y-%m-%dT%H:%M:%SZ",
+                    ).strftime(datetime_format)
+        else:
+            if stdout:
+                commit = stdout.splitlines()
+                git_log["commit"] = commit[0].split(" ")[-1]
+                commit_date = commit[2].lstrip("Date:").strip()
+                git_log["date"] = commit_date.rsplit(" ", 1)[0]
+    return git_log
+
+
 html_page_footer_pages_path = (
     os.getenv("HTML_PAGE_FOOTER_PAGES_PATH")
     if os.getenv("HTML_PAGE_FOOTER_PAGES_PATH")
@@ -79,14 +261,6 @@ pgm = sys.argv[1]
 src_file = "%s.html" % pgm
 tmp_file = "%s.tmp.html" % pgm
 
-grass_version = os.getenv("VERSION_NUMBER", "unknown")
-trunk_url = ""
-addons_url = ""
-if grass_version != "unknown":
-    major, minor, patch = grass_version.split(".")
-    trunk_url = f"https://github.com/OSGeo/grass/tree/releasebranch_{major}_{minor}/"
-    addons_url = f"https://github.com/OSGeo/grass-addons/tree/grass{major}/"
-
 header_base = """<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
 <html>
 <head>
@@ -121,6 +295,9 @@ sourcecode = string.Template(
   <a href="${URL_SOURCE}">${PGM} source code</a>
   (<a href="${URL_LOG}">history</a>)
 </p>
+<p>
+  ${DATE_TAG}
+</p>
 """
 )
 
@@ -447,6 +624,7 @@ else:
     source_url = addons_url
     pgmdir = os.path.sep.join(curdir.split(os.path.sep)[-3:])
 url_source = ""
+addon_path = None
 if os.getenv("SOURCE_URL", ""):
     addon_path = get_addon_path()
     if addon_path:
@@ -477,11 +655,23 @@ if index_name:
     else:
         url_log = url_source.replace(tree, commits)
 
+    git_commit = get_last_git_commit(
+        src_dir=curdir,
+        addon_path=addon_path if addon_path else None,
+        is_addon=True if addon_path else False,
+    )
+    if git_commit["commit"] == "unknown":
+        date_tag = "Accessed: {date}".format(date=git_commit["date"])
+    else:
+        date_tag = "Latest change: {date} in commit: {commit}".format(
+            date=git_commit["date"], commit=git_commit["commit"]
+        )
     sys.stdout.write(
         sourcecode.substitute(
             URL_SOURCE=url_source,
             PGM=pgm,
             URL_LOG=url_log,
+            DATE_TAG=date_tag,
         )
     )
     sys.stdout.write(


=====================================
vector/v.distance/main.c
=====================================
@@ -266,7 +266,7 @@ int main(int argc, char *argv[])
     opt.to_field->guidependency = G_store(opt.to_column->key);
 
     G_option_required(opt.out, opt.table, flag.print, opt.column, NULL);
-    G_option_exclusive(opt.out, opt.table, flag.print, NULL);
+    G_option_excludes(flag.print, opt.out, opt.table, NULL);
     G_option_requires(opt.upload, flag.print, opt.table, opt.column, NULL);
     G_option_requires(opt.column, opt.upload, NULL);
     G_option_requires(flag.print, opt.upload, NULL);



View it on GitLab: https://salsa.debian.org/debian-gis-team/grass/-/commit/7b128c132e838d0db411445953792270437ea627

-- 
View it on GitLab: https://salsa.debian.org/debian-gis-team/grass/-/commit/7b128c132e838d0db411445953792270437ea627
You're receiving this email because of your account on salsa.debian.org.


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/pkg-grass-devel/attachments/20220203/83c0db81/attachment-0001.htm>


More information about the Pkg-grass-devel mailing list