Repository: openSUSE/osc

Issue:
Choke on checkout of scmsync-enabled package
**Versions**
- Operating system: Tumbleweed 20250207 x86_64
- Packages: osc-1.12.0-1.1

**To Reproduce**

```
20:20 a4:../zu/science $ osc meta pkg gap-lpres
<package name="gap-lpres" project="science">
  <title/>
  <description/>
  <scmsync>https://src.opensuse.org/jengelh/gap-lpres</scmsync>
  <person userid="jengelh" role="maintainer"/>
</package>

20:20 a4:../zu/science $ git clone https://src.opensuse.org/jengelh/gap-lpres
Cloning into 'gap-lpres'...
remote: Enumerating objects: 24, done.
remote: Total 24 (delta 0), reused 0 (delta 0), pack-reused 24 (from 1)
Receiving objects: 100% (24/24), 4.65 KiB | 4.65 MiB/s, done.
Resolving deltas: 100% (8/8), done.
20:20 a4:../zu/science $ l gap-lpres
total 476
drwxr-xr-x  3 ej ej    159 Feb  9 20:20 .
drwxr-xr-x 36 ej ej   4096 Feb  9 20:20 ..
drwxr-xr-x  8 ej ej   4096 Feb  9 20:20 .git
-rw-r--r--  1 ej ej    941 Feb  9 20:20 .gitattributes
-rw-r--r--  1 ej ej      5 Feb  9 20:20 .gitignore
-rw-r--r--  1 ej ej    775 Feb  9 20:20 gap-lpres.changes
-rw-r--r--  1 ej ej   1879 Feb  9 20:20 gap-lpres.spec
-rw-r--r--  1 ej ej 462641 Feb  9 20:20 lpres-1.0.3.tar.gz
20:20 a4:../zu/science $ rm -rf gap-lpres
20:20 a4:../zu/science $ osc co gap-lpres
A    /home/ej/obs_nosave/zu/science/gap-lpres
ERROR: git clone failed:  Cloning into '/home/ej/obs_nosave/zu/science/gap-lpres'...
fatal: Remote branch None not found in upstream origin
fatal: the remote end hung up unexpectedly
Gitea: Failed to execute git command
```

`None` sounds very suspect.

**Expected behavior**

succeed when the scmsync line has no `#branch` specifier.

**Additional context**

New bug since https://github.com/openSUSE/osc/issues/1687

(I'm switching the scmsync line to have a #branch specifier to be able to continue; it can be reproduced with other BuildService package entities)

Hints from issue comments:
@dmach: It seems to be a problem with obs-scm-bridge rather than osc, because it does the cloning.

I can't reproduce by running `osc co science/gap-lpres` while using the following packages:
```
obs-scm-bridge-0.6.1-1.1.noarch
osc-2.0~pre.1737057854.289bf02-1241.1.noarch  # a test build from master, probably indentical to 1.12.0
```@adrianschroeter: I see that osc is extending the url with #None when calling the bridge.

The problem is that scmsync_obsinfo.revision gets always added, even when not set in checkout_package from osc/core.py@dmach: @adrianschroeter thanks for pointing me in that direction. I obviously hacked my test code in a wrong place, completely skipping this code path. Let me take a look.

Solution patch:
diff --git a/contrib/osc.spec b/contrib/osc.spec
index 82c9c573..af53cc3c 100644
--- a/contrib/osc.spec
+++ b/contrib/osc.spec
@@ -104,6 +104,8 @@ Recommends:     %{use_python_pkg}-keyring-keyutils
 Recommends:     %{use_python_pkg}-zstandard
 
 Recommends:     %{obs_build_pkg}
+# need support for --repos-directory option
+Conflicts:      %{obs_build_pkg} < 20250206
 Recommends:     ca-certificates
 Recommends:     diffstat
 Recommends:     powerpc32
diff --git a/osc/build.py b/osc/build.py
index 46c89209..794c350b 100644
--- a/osc/build.py
+++ b/osc/build.py
@@ -1273,9 +1273,9 @@ def main(apiurl, store, opts, argv):
 
     # Make packages from buildinfo available as repos for kiwi/docker/fissile
     if build_type in ('kiwi', 'docker', 'podman', 'fissile', 'productcompose'):
-        if os.path.exists('repos'):
+        if os.path.lexists('repos'):
             shutil.rmtree('repos')
-        if os.path.exists('containers'):
+        if os.path.lexists('containers'):
             shutil.rmtree('containers')
         os.mkdir('repos')
         for i in bi.deps:
@@ -1349,6 +1349,10 @@ def main(apiurl, store, opts, argv):
                 else:
                     os.symlink(path, tffn)
 
+    else:
+        buildargs.append("--repos-directory=-")
+        buildargs.append("--containers-directory=-")
+
     if build_type == 'kiwi':
         # Is a obsrepositories tag used?
         try:
diff --git a/osc/commandline.py b/osc/commandline.py
index 2f63bb96..bff77369 100644
--- a/osc/commandline.py
+++ b/osc/commandline.py
@@ -4259,7 +4259,14 @@ Please submit there instead, or use --nodevelproject to force direct submission.
             print(f'Using target project \'{target_project}\'{release_in}')
 
         if not opts.message:
-            opts.message = edit_message()
+            msg = ""
+            if opts.supersede:
+                from .obs_api import Request
+
+                req = Request.from_api(apiurl, opts.supersede)
+                msg = req.description + "\n"
+            opts.message = edit_message(template=f"{msg}")
+
 
         supersede_existing = False
         reqs = []
diff --git a/osc/core.py b/osc/core.py
index 00ed806c..ca07dd2e 100644
--- a/osc/core.py
+++ b/osc/core.py
@@ -2796,7 +2796,9 @@ def binary(data: bytes):
 
     We're using heuristics according to OBS: src/backend/BSSrcServer/filediff - look for "diff binary detection"
     """
-    binary_chars = re.findall(b"[\x00-\0x07\x0e-\x1f]", data)
+    if b"\0" in data:
+        return True
+    binary_chars = re.findall(b"[\x00-\x07\x0e-\x1f]", data)
     return len(binary_chars) * 40 > len(data)
 
 
@@ -3176,7 +3178,10 @@ def checkout_package(
             # we need also take into account that the url was different at that point of time
             from .obs_api.scmsync_obsinfo import ScmsyncObsinfo
             scmsync_obsinfo = ScmsyncObsinfo.from_api(apiurl, project, package, rev=revision)
-            scm_url = f"{scmsync_obsinfo.url}#{scmsync_obsinfo.revision}"
+            if scmsync_obsinfo.revision:
+                scm_url = f"{scmsync_obsinfo.url}#{scmsync_obsinfo.revision}"
+            else:
+                scm_url = f"{scmsync_obsinfo.url}"
 
         run_obs_scm_bridge(url=scm_url, target_dir=directory)
 


Test patch:
diff --git a/tests/test_core.py b/tests/test_core.py
index 1303645c..9009d967 100644
--- a/tests/test_core.py
+++ b/tests/test_core.py
@@ -159,12 +159,12 @@ class TestBinaryFile(unittest.TestCase):
         path = os.path.join(self.tmpdir, "binary")
         with open(path, "wb") as f:
             f.write(1000 * b"a")
-            f.write(b"\0")
+            f.write(b"\1")
         self.assertFalse(binary_file(path))
 
         with open(path, "wb") as f:
             f.write(4096 * b"a")
-            f.write(b"\0")
+            f.write(b"\1")
         self.assertFalse(binary_file(path))
 
     def test_binary(self):
@@ -173,19 +173,26 @@ class TestBinaryFile(unittest.TestCase):
         # sufficient control chars in first 4k
         with open(path, "wb") as f:
             f.write(1000 * b"a")
-            f.write(26 * b"\0")
+            f.write(26 * b"\1")
         self.assertTrue(binary_file(path))
 
         # sufficient control chars in first 4k
         with open(path, "wb") as f:
             f.write(3993 * b"a")
-            f.write(103 * b"\0")
+            f.write(103 * b"\1")
+        self.assertTrue(binary_file(path))
+
+        # a single \0 is good enough for us to say it's a binary file
+        with open(path, "wb") as f:
+            f.write(3993 * b"a")
+            f.write(b"\0")
+            f.write(999 * b"\1")
         self.assertTrue(binary_file(path))
 
         # detected as text because we're reading only first 4k characters
         with open(path, "wb") as f:
             f.write(4096 * b"a")
-            f.write(1000 * b"\0")
+            f.write(1000 * b"\1")
         self.assertFalse(binary_file(path))