{"thread":{"id":"63409","subject":"[PATCH] git-p4: preserve executable bit in LFS pointers","startedAt":"2025-05-06T23:29:52Z","lastAt":"2025-05-06T23:29:52Z","messageCount":1,"participants":["Scott Guest via GitGitGadget"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"517404","messageId":"pull.1917.git.1746574189008.gitgitgadget@gmail.com","threadId":"63409","inReplyTo":null,"subject":"[PATCH] git-p4: preserve executable bit in LFS pointers","fromName":"Scott Guest via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2025-05-06T23:29:48Z","receivedAt":"2025-05-06T23:29:52Z","isPatch":true,"sender":{"key":"name:Scott Guest","avatar":null},"body":"From: Scott Guest <sguest@nvidia.com>\n\ngit-p4.py currently marks all Git LFS pointers non-executable,\nwhen it should instead match the executable bit of the stored file.\n\nThe LFS spec made this change nearly a decade ago, see\nhttps://github.com/git-lfs/git-lfs/commit/8d075a8\n\nSigned-off-by: Scott Guest <sguest@nvidia.com>\n---\n    git-p4: preserve executable bit in LFS pointers\n\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-1917%2FScott-Guest%2Fp4-lfs-exec-fix-v1\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1917/Scott-Guest/p4-lfs-exec-fix-v1\nPull-Request: https://github.com/gitgitgadget/git/pull/1917\n\n git-p4.py | 34 +++++++++++++---------------------\n 1 file changed, 13 insertions(+), 21 deletions(-)\n\ndiff --git a/git-p4.py b/git-p4.py\nindex c0ca7becaf4..adfb5988492 100755\n--- a/git-p4.py\n+++ b/git-p4.py\n@@ -1463,7 +1463,7 @@ class LargeFileSystem(object):\n         self.largeFiles = set()\n         self.writeToGitStream = writeToGitStream\n \n-    def generatePointer(self, cloneDestination, contentFile):\n+    def generatePointer(self, contentFile):\n         \"\"\"Return the content of a pointer file that is stored in Git instead\n            of the actual content.\n            \"\"\"\n@@ -1517,20 +1517,14 @@ class LargeFileSystem(object):\n     def isLargeFile(self, relPath):\n         return relPath in self.largeFiles\n \n-    def processContent(self, git_mode, relPath, contents):\n+    def processContent(self, relPath, contents):\n         \"\"\"Processes the content of git fast import. This method decides if a\n            file is stored in the large file system and handles all necessary\n            steps.\n            \"\"\"\n-        # symlinks aren't processed by smudge/clean filters\n-        if git_mode == \"120000\":\n-            return (git_mode, contents)\n-\n         if self.exceedsLargeFileThreshold(relPath, contents) or self.hasLargeFileExtension(relPath):\n             contentTempFile = self.generateTempFile(contents)\n-            pointer_git_mode, contents, localLargeFile = self.generatePointer(contentTempFile)\n-            if pointer_git_mode:\n-                git_mode = pointer_git_mode\n+            contents, localLargeFile = self.generatePointer(contentTempFile)\n             if localLargeFile:\n                 # Move temp file to final location in large file system\n                 largeFileDir = os.path.dirname(localLargeFile)\n@@ -1542,7 +1536,7 @@ class LargeFileSystem(object):\n                     self.pushFile(localLargeFile)\n                 if verbose:\n                     sys.stderr.write(\"%s moved to large file system (%s)\\n\" % (relPath, localLargeFile))\n-        return (git_mode, contents)\n+        return contents\n \n \n class MockLFS(LargeFileSystem):\n@@ -1555,10 +1549,9 @@ class MockLFS(LargeFileSystem):\n            \"\"\"\n         with open(contentFile, 'r') as f:\n             content = next(f)\n-            gitMode = '100644'\n             pointerContents = 'pointer-' + content\n             localLargeFile = os.path.join(os.getcwd(), '.git', 'mock-storage', 'local', content[:-1])\n-            return (gitMode, pointerContents, localLargeFile)\n+            return (pointerContents, localLargeFile)\n \n     def pushFile(self, localLargeFile):\n         \"\"\"The remote filename of the large file storage is the same as the\n@@ -1586,7 +1579,7 @@ class GitLFS(LargeFileSystem):\n            content.\n            \"\"\"\n         if os.path.getsize(contentFile) == 0:\n-            return (None, '', None)\n+            return ('', None)\n \n         pointerProcess = subprocess.Popen(\n             ['git', 'lfs', 'pointer', '--file=' + contentFile],\n@@ -1616,9 +1609,7 @@ class GitLFS(LargeFileSystem):\n             'objects', oid[:2], oid[2:4],\n             oid,\n         )\n-        # LFS Spec states that pointer files should not have the executable bit set.\n-        gitMode = '100644'\n-        return (gitMode, pointerFile, localLargeFile)\n+        return (pointerFile, localLargeFile)\n \n     def pushFile(self, localLargeFile):\n         uploadProcess = subprocess.Popen(\n@@ -1652,12 +1643,12 @@ class GitLFS(LargeFileSystem):\n         LargeFileSystem.removeLargeFile(self, relPath)\n         self.writeToGitStream('100644', '.gitattributes', self.generateGitAttributes())\n \n-    def processContent(self, git_mode, relPath, contents):\n+    def processContent(self, relPath, contents):\n         if relPath == '.gitattributes':\n             self.baseGitAttributes = contents\n-            return (git_mode, self.generateGitAttributes())\n+            return self.generateGitAttributes()\n         else:\n-            return LargeFileSystem.processContent(self, git_mode, relPath, contents)\n+            return LargeFileSystem.processContent(self, relPath, contents)\n \n \n class Command:\n@@ -3217,8 +3208,9 @@ class P4Sync(Command, P4UserMap):\n         if regexp:\n             contents = [regexp.sub(br'$\\1$', c) for c in contents]\n \n-        if self.largeFileSystem:\n-            git_mode, contents = self.largeFileSystem.processContent(git_mode, relPath, contents)\n+        # symlinks aren't processed by smudge/clean filters\n+        if git_mode != '120000' and self.largeFileSystem:\n+            contents = self.largeFileSystem.processContent(relPath, contents)\n \n         self.writeToGitStream(git_mode, relPath, contents)\n \n\nbase-commit: 6c0bd1fc70efaf053abe4e57c976afdc72d15377\n-- \ngitgitgadget\n"}]}