diff --git a/.basedpyright/baseline.json b/.basedpyright/baseline.json index 14bf1024e..79d35235e 100644 --- a/.basedpyright/baseline.json +++ b/.basedpyright/baseline.json @@ -1,966 +1,502 @@ { - "files": { - "./git/cmd.py": [ - { - "code": "reportGeneralTypeIssues", - "range": { - "startColumn": 13, - "endColumn": 32, - "lineCount": 1 - } - }, - { - "code": "reportOptionalOperand", - "range": { - "startColumn": 27, - "endColumn": 35, - "lineCount": 1 - } - }, - { - "code": "reportReturnType", - "range": { - "startColumn": 28, - "endColumn": 31, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 28, - "endColumn": 40, - "lineCount": 1 - } - }, - { - "code": "reportReturnType", - "range": { - "startColumn": 19, - "endColumn": 68, - "lineCount": 1 - } - }, - { - "code": "reportReturnType", - "range": { - "startColumn": 19, - "endColumn": 31, - "lineCount": 1 - } - } - ], - "./git/config.py": [ - { - "code": "reportGeneralTypeIssues", - "range": { - "startColumn": 11, - "endColumn": 26, - "lineCount": 1 - } - }, - { - "code": "reportInvalidTypeVarUse", - "range": { - "startColumn": 43, - "endColumn": 45, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 41, - "endColumn": 48, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 32, - "endColumn": 39, - "lineCount": 1 - } - }, - { - "code": "reportCallIssue", - "range": { - "startColumn": 25, - "endColumn": 46, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 30, - "endColumn": 39, - "lineCount": 1 - } - } - ], - "./git/db.py": [ - { - "code": "reportIncompatibleMethodOverride", - "range": { - "startColumn": 8, - "endColumn": 12, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 61, - "endColumn": 79, - "lineCount": 1 - } - }, - { - "code": "reportIncompatibleMethodOverride", - "range": { - "startColumn": 8, - "endColumn": 14, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 70, - "endColumn": 88, - "lineCount": 1 - } - } - ], - "./git/index/base.py": [ - { - "code": "reportArgumentType", - "range": { - "startColumn": 30, - "endColumn": 36, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 28, - "endColumn": 34, - "lineCount": 1 - } - }, - { - "code": "reportAssignmentType", - "range": { - "startColumn": 38, - "endColumn": 76, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 45, - "endColumn": 53, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 60, - "endColumn": 63, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 56, - "endColumn": 60, - "lineCount": 1 - } - }, - { - "code": "reportSelfClsParameterName", - "range": { - "startColumn": 30, - "endColumn": 33, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 51, - "endColumn": 57, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 55, - "endColumn": 61, - "lineCount": 1 - } - }, - { - "code": "reportIncompatibleMethodOverride", - "range": { - "startColumn": 8, - "endColumn": 12, - "lineCount": 1 - } - }, - { - "code": "reportAssignmentType", - "range": { - "startColumn": 20, - "endColumn": 46, - "lineCount": 1 - } - } - ], - "./git/index/fun.py": [ - { - "code": "reportArgumentType", - "range": { - "startColumn": 8, - "endColumn": 36, - "lineCount": 1 - } - } - ], - "./git/index/typ.py": [ - { - "code": "reportArgumentType", - "range": { - "startColumn": 37, - "endColumn": 46, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 37, - "endColumn": 46, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 37, - "endColumn": 46, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 37, - "endColumn": 46, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 37, - "endColumn": 46, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 37, - "endColumn": 46, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 37, - "endColumn": 46, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 37, - "endColumn": 46, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 37, - "endColumn": 46, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 37, - "endColumn": 46, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 37, - "endColumn": 46, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 37, - "endColumn": 46, - "lineCount": 1 - } - } - ], - "./git/objects/base.py": [ - { - "code": "reportArgumentType", - "range": { - "startColumn": 20, - "endColumn": 27, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 29, - "endColumn": 36, - "lineCount": 1 - } - } - ], - "./git/objects/blob.py": [ - { - "code": "reportIncompatibleVariableOverride", - "range": { - "startColumn": 4, - "endColumn": 8, - "lineCount": 1 - } - } - ], - "./git/objects/commit.py": [ - { - "code": "reportIncompatibleVariableOverride", - "range": { - "startColumn": 4, - "endColumn": 8, - "lineCount": 1 - } - }, - { - "code": "reportIncompatibleMethodOverride", - "range": { - "startColumn": 8, - "endColumn": 31, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 20, - "endColumn": 33, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 20, - "endColumn": 30, - "lineCount": 1 - } - } - ], - "./git/objects/submodule/base.py": [ - { - "code": "reportAttributeAccessIssue", - "range": { - "startColumn": 19, - "endColumn": 24, - "lineCount": 1 - } - }, - { - "code": "reportAttributeAccessIssue", - "range": { - "startColumn": 19, - "endColumn": 24, - "lineCount": 1 - } - }, - { - "code": "reportReturnType", - "range": { - "startColumn": 23, - "endColumn": 25, - "lineCount": 1 - } - }, - { - "code": "reportReturnType", - "range": { - "startColumn": 23, - "endColumn": 25, - "lineCount": 1 - } - }, - { - "code": "reportAttributeAccessIssue", - "range": { - "startColumn": 41, - "endColumn": 46, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 52, - "endColumn": 84, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 20, - "endColumn": 40, - "lineCount": 1 - } - }, - { - "code": "reportAttributeAccessIssue", - "range": { - "startColumn": 35, - "endColumn": 42, - "lineCount": 1 - } - }, - { - "code": "reportAttributeAccessIssue", - "range": { - "startColumn": 15, - "endColumn": 20, - "lineCount": 1 - } - }, - { - "code": "reportAttributeAccessIssue", - "range": { - "startColumn": 15, - "endColumn": 20, - "lineCount": 1 - } - }, - { - "code": "reportAttributeAccessIssue", - "range": { - "startColumn": 19, - "endColumn": 33, - "lineCount": 1 - } - }, - { - "code": "reportAttributeAccessIssue", - "range": { - "startColumn": 19, - "endColumn": 33, - "lineCount": 1 - } - }, - { - "code": "reportAttributeAccessIssue", - "range": { - "startColumn": 15, - "endColumn": 27, - "lineCount": 1 - } - }, - { - "code": "reportAttributeAccessIssue", - "range": { - "startColumn": 15, - "endColumn": 27, - "lineCount": 1 - } - }, - { - "code": "reportAttributeAccessIssue", - "range": { - "startColumn": 15, - "endColumn": 19, - "lineCount": 1 - } - }, - { - "code": "reportAttributeAccessIssue", - "range": { - "startColumn": 15, - "endColumn": 19, - "lineCount": 1 - } - }, - { - "code": "reportReturnType", - "range": { - "startColumn": 18, - "endColumn": 20, - "lineCount": 1 - } - } - ], - "./git/objects/tag.py": [ - { - "code": "reportIncompatibleVariableOverride", - "range": { - "startColumn": 4, - "endColumn": 8, - "lineCount": 1 - } - }, - { - "code": "reportAttributeAccessIssue", - "range": { - "startColumn": 72, - "endColumn": 78, - "lineCount": 1 - } - } - ], - "./git/objects/tree.py": [ - { - "code": "reportIncompatibleVariableOverride", - "range": { - "startColumn": 4, - "endColumn": 8, - "lineCount": 1 - } - }, - { - "code": "reportIncompatibleMethodOverride", - "range": { - "startColumn": 8, - "endColumn": 31, - "lineCount": 1 - } - }, - { - "code": "reportReturnType", - "range": { - "startColumn": 19, - "endColumn": 83, - "lineCount": 1 - } - }, - { - "code": "reportReturnType", - "range": { - "startColumn": 15, - "endColumn": 54, - "lineCount": 1 - } - } - ], - "./git/objects/util.py": [ - { - "code": "reportAssignmentType", - "range": { - "startColumn": 23, - "endColumn": 34, - "lineCount": 1 - } - }, - { - "code": "reportReturnType", - "range": { - "startColumn": 22, - "endColumn": 26, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 30, - "endColumn": 34, - "lineCount": 1 - } - }, - { - "code": "reportReturnType", - "range": { - "startColumn": 15, - "endColumn": 54, - "lineCount": 1 - } - } - ], - "./git/refs/log.py": [ - { - "code": "reportArgumentType", - "range": { - "startColumn": 30, - "endColumn": 34, - "lineCount": 1 - } - }, - { - "code": "reportAttributeAccessIssue", - "range": { - "startColumn": 17, - "endColumn": 22, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 28, - "endColumn": 30, - "lineCount": 1 - } - } - ], - "./git/refs/reference.py": [ - { - "code": "reportInvalidTypeVarUse", - "range": { - "startColumn": 22, - "endColumn": 34, - "lineCount": 1 - } - }, - { - "code": "reportIncompatibleVariableOverride", - "range": { - "startColumn": 13, - "endColumn": 17, - "lineCount": 1 - } - } - ], - "./git/refs/symbolic.py": [ - { - "code": "reportAttributeAccessIssue", - "range": { - "startColumn": 15, - "endColumn": 20, - "lineCount": 1 - } - } - ], - "./git/refs/tag.py": [ - { - "code": "reportIncompatibleMethodOverride", - "range": { - "startColumn": 8, - "endColumn": 14, - "lineCount": 1 - } - }, - { - "code": "reportIncompatibleMethodOverride", - "range": { - "startColumn": 8, - "endColumn": 14, - "lineCount": 1 - } - } - ], - "./git/remote.py": [ - { - "code": "reportArgumentType", - "range": { - "startColumn": 25, - "endColumn": 36, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 29, - "endColumn": 40, - "lineCount": 1 - } - }, - { - "code": "reportAttributeAccessIssue", - "range": { - "startColumn": 26, - "endColumn": 38, - "lineCount": 1 - } - }, - { - "code": "reportAttributeAccessIssue", - "range": { - "startColumn": 26, - "endColumn": 38, - "lineCount": 1 - } - }, - { - "code": "reportAttributeAccessIssue", - "range": { - "startColumn": 26, - "endColumn": 38, - "lineCount": 1 - } - }, - { - "code": "reportAttributeAccessIssue", - "range": { - "startColumn": 26, - "endColumn": 38, - "lineCount": 1 - } - } - ], - "./git/repo/base.py": [ - { - "code": "reportRedeclaration", - "range": { - "startColumn": 4, - "endColumn": 15, - "lineCount": 1 - } - }, - { - "code": "reportAttributeAccessIssue", - "range": { - "startColumn": 18, - "endColumn": 22, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 35, - "endColumn": 41, - "lineCount": 1 - } - }, - { - "code": "reportReturnType", - "range": { - "startColumn": 15, - "endColumn": 46, - "lineCount": 1 - } - }, - { - "code": "reportReturnType", - "range": { - "startColumn": 15, - "endColumn": 51, - "lineCount": 1 - } - }, - { - "code": "reportReturnType", - "range": { - "startColumn": 15, - "endColumn": 28, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 16, - "endColumn": 31, - "lineCount": 1 - } - }, - { - "code": "reportTypedDictNotRequiredAccess", - "range": { - "startColumn": 21, - "endColumn": 31, - "lineCount": 1 - } - }, - { - "code": "reportTypedDictNotRequiredAccess", - "range": { - "startColumn": 34, - "endColumn": 44, - "lineCount": 1 - } - }, - { - "code": "reportTypedDictNotRequiredAccess", - "range": { - "startColumn": 65, - "endColumn": 79, - "lineCount": 1 - } - }, - { - "code": "reportTypedDictNotRequiredAccess", - "range": { - "startColumn": 82, - "endColumn": 102, - "lineCount": 1 - } - }, - { - "code": "reportTypedDictNotRequiredAccess", - "range": { - "startColumn": 50, - "endColumn": 69, - "lineCount": 1 - } - }, - { - "code": "reportTypedDictNotRequiredAccess", - "range": { - "startColumn": 68, - "endColumn": 85, - "lineCount": 1 - } - }, - { - "code": "reportTypedDictNotRequiredAccess", - "range": { - "startColumn": 88, - "endColumn": 111, - "lineCount": 1 - } - }, - { - "code": "reportTypedDictNotRequiredAccess", - "range": { - "startColumn": 51, - "endColumn": 73, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 12, - "endColumn": 26, - "lineCount": 1 - } - }, - { - "code": "reportAssignmentType", - "range": { - "startColumn": 22, - "endColumn": 38, - "lineCount": 1 - } - } - ], - "./git/repo/fun.py": [ - { - "code": "reportArgumentType", - "range": { - "startColumn": 25, - "endColumn": 33, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 35, - "endColumn": 43, - "lineCount": 1 - } - }, - { - "code": "reportAssignmentType", - "range": { - "startColumn": 18, - "endColumn": 28, - "lineCount": 1 - } - }, - { - "code": "reportReturnType", - "range": { - "startColumn": 11, - "endColumn": 14, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 24, - "endColumn": 27, - "lineCount": 1 - } - }, - { - "code": "reportReturnType", - "range": { - "startColumn": 11, - "endColumn": 14, - "lineCount": 1 - } - }, - { - "code": "reportReturnType", - "range": { - "startColumn": 11, - "endColumn": 20, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 25, - "endColumn": 28, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 24, - "endColumn": 27, - "lineCount": 1 - } - } - ], - "./test/deprecation/test_basic.py": [ - { - "code": "reportUnusedExpression", - "range": { - "startColumn": 12, - "endColumn": 62, - "lineCount": 1 - } - } - ] - } + "files": { + "./git/config.py": [ + { + "code": "reportGeneralTypeIssues", + "range": { + "startColumn": 11, + "endColumn": 26, + "lineCount": 1 + } + }, + { + "code": "reportInvalidTypeVarUse", + "range": { + "startColumn": 43, + "endColumn": 45, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 41, + "endColumn": 48, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 32, + "endColumn": 39, + "lineCount": 1 + } + }, + { + "code": "reportCallIssue", + "range": { + "startColumn": 25, + "endColumn": 46, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 30, + "endColumn": 39, + "lineCount": 1 + } + } + ], + "./git/db.py": [ + { + "code": "reportIncompatibleMethodOverride", + "range": { + "startColumn": 8, + "endColumn": 12, + "lineCount": 1 + } + }, + { + "code": "reportIncompatibleMethodOverride", + "range": { + "startColumn": 8, + "endColumn": 14, + "lineCount": 1 + } + } + ], + "./git/index/base.py": [ + { + "code": "reportArgumentType", + "range": { + "startColumn": 30, + "endColumn": 36, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 28, + "endColumn": 34, + "lineCount": 1 + } + }, + { + "code": "reportAssignmentType", + "range": { + "startColumn": 38, + "endColumn": 76, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 51, + "endColumn": 57, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 55, + "endColumn": 61, + "lineCount": 1 + } + }, + { + "code": "reportIncompatibleMethodOverride", + "range": { + "startColumn": 8, + "endColumn": 12, + "lineCount": 1 + } + }, + { + "code": "reportAssignmentType", + "range": { + "startColumn": 20, + "endColumn": 46, + "lineCount": 1 + } + } + ], + "./git/objects/blob.py": [ + { + "code": "reportIncompatibleVariableOverride", + "range": { + "startColumn": 4, + "endColumn": 8, + "lineCount": 1 + } + } + ], + "./git/objects/commit.py": [ + { + "code": "reportIncompatibleVariableOverride", + "range": { + "startColumn": 4, + "endColumn": 8, + "lineCount": 1 + } + }, + { + "code": "reportIncompatibleMethodOverride", + "range": { + "startColumn": 8, + "endColumn": 31, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 20, + "endColumn": 33, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 20, + "endColumn": 30, + "lineCount": 1 + } + } + ], + "./git/objects/submodule/base.py": [ + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 19, + "endColumn": 24, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 19, + "endColumn": 24, + "lineCount": 1 + } + }, + { + "code": "reportReturnType", + "range": { + "startColumn": 23, + "endColumn": 25, + "lineCount": 1 + } + }, + { + "code": "reportReturnType", + "range": { + "startColumn": 23, + "endColumn": 25, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 52, + "endColumn": 84, + "lineCount": 1 + } + } + ], + "./git/objects/tag.py": [ + { + "code": "reportIncompatibleVariableOverride", + "range": { + "startColumn": 4, + "endColumn": 8, + "lineCount": 1 + } + } + ], + "./git/objects/tree.py": [ + { + "code": "reportIncompatibleVariableOverride", + "range": { + "startColumn": 4, + "endColumn": 8, + "lineCount": 1 + } + }, + { + "code": "reportIncompatibleMethodOverride", + "range": { + "startColumn": 8, + "endColumn": 31, + "lineCount": 1 + } + }, + { + "code": "reportReturnType", + "range": { + "startColumn": 19, + "endColumn": 83, + "lineCount": 1 + } + }, + { + "code": "reportReturnType", + "range": { + "startColumn": 15, + "endColumn": 54, + "lineCount": 1 + } + } + ], + "./git/objects/util.py": [ + { + "code": "reportAssignmentType", + "range": { + "startColumn": 23, + "endColumn": 34, + "lineCount": 1 + } + }, + { + "code": "reportReturnType", + "range": { + "startColumn": 22, + "endColumn": 26, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 30, + "endColumn": 34, + "lineCount": 1 + } + }, + { + "code": "reportReturnType", + "range": { + "startColumn": 15, + "endColumn": 54, + "lineCount": 1 + } + } + ], + "./git/refs/log.py": [ + { + "code": "reportArgumentType", + "range": { + "startColumn": 30, + "endColumn": 34, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 17, + "endColumn": 22, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 28, + "endColumn": 30, + "lineCount": 1 + } + } + ], + "./git/refs/reference.py": [ + { + "code": "reportIncompatibleVariableOverride", + "range": { + "startColumn": 13, + "endColumn": 17, + "lineCount": 1 + } + } + ], + "./git/refs/symbolic.py": [ + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 15, + "endColumn": 20, + "lineCount": 1 + } + } + ], + "./git/refs/tag.py": [ + { + "code": "reportIncompatibleMethodOverride", + "range": { + "startColumn": 8, + "endColumn": 14, + "lineCount": 1 + } + }, + { + "code": "reportIncompatibleMethodOverride", + "range": { + "startColumn": 8, + "endColumn": 14, + "lineCount": 1 + } + } + ], + "./git/remote.py": [ + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 26, + "endColumn": 38, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 26, + "endColumn": 38, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 26, + "endColumn": 38, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 26, + "endColumn": 38, + "lineCount": 1 + } + } + ], + "./git/repo/base.py": [ + { + "code": "reportReturnType", + "range": { + "startColumn": 15, + "endColumn": 46, + "lineCount": 1 + } + }, + { + "code": "reportReturnType", + "range": { + "startColumn": 15, + "endColumn": 51, + "lineCount": 1 + } + }, + { + "code": "reportReturnType", + "range": { + "startColumn": 15, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportTypedDictNotRequiredAccess", + "range": { + "startColumn": 21, + "endColumn": 31, + "lineCount": 1 + } + }, + { + "code": "reportTypedDictNotRequiredAccess", + "range": { + "startColumn": 34, + "endColumn": 44, + "lineCount": 1 + } + }, + { + "code": "reportTypedDictNotRequiredAccess", + "range": { + "startColumn": 65, + "endColumn": 79, + "lineCount": 1 + } + }, + { + "code": "reportTypedDictNotRequiredAccess", + "range": { + "startColumn": 82, + "endColumn": 102, + "lineCount": 1 + } + }, + { + "code": "reportTypedDictNotRequiredAccess", + "range": { + "startColumn": 50, + "endColumn": 69, + "lineCount": 1 + } + }, + { + "code": "reportTypedDictNotRequiredAccess", + "range": { + "startColumn": 68, + "endColumn": 85, + "lineCount": 1 + } + }, + { + "code": "reportTypedDictNotRequiredAccess", + "range": { + "startColumn": 88, + "endColumn": 111, + "lineCount": 1 + } + }, + { + "code": "reportTypedDictNotRequiredAccess", + "range": { + "startColumn": 51, + "endColumn": 73, + "lineCount": 1 + } + } + ], + "./git/repo/fun.py": [ + { + "code": "reportReturnType", + "range": { + "startColumn": 11, + "endColumn": 20, + "lineCount": 1 + } + } + ], + "./test/deprecation/test_basic.py": [ + { + "code": "reportUnusedExpression", + "range": { + "startColumn": 12, + "endColumn": 62, + "lineCount": 1 + } + } + ] + } } diff --git a/git/cmd.py b/git/cmd.py index 193dfd4f6..3d7446906 100644 --- a/git/cmd.py +++ b/git/cmd.py @@ -100,7 +100,7 @@ def handle_process_output( - process: "Git.AutoInterrupt" | Popen, + process: Union["Git.AutoInterrupt", Popen], stdout_handler: Union[ None, Callable[[AnyStr], None], @@ -395,9 +395,7 @@ def wait(self, stderr: Union[None, str, bytes] = b"") -> int: :raise git.exc.GitCommandError: If the return status is not 0. """ - if stderr is None: - stderr_b = b"" - stderr_b = force_bytes(data=stderr, encoding="utf-8") + stderr_b = force_bytes(data=stderr, encoding="utf-8") or b"" status: Union[int, None] if self.proc is not None: status = self.proc.wait() @@ -1180,52 +1178,112 @@ def version_info(self) -> Tuple[int, ...]: def execute( self, command: Union[str, Sequence[Any]], + istream: Union[None, int, BinaryIO] = None, *, as_process: Literal[True], + **subprocess_kwargs: Any, ) -> "AutoInterrupt": ... @overload def execute( self, command: Union[str, Sequence[Any]], + istream: Union[None, int, BinaryIO] = None, *, as_process: Literal[False] = False, - stdout_as_string: Literal[True], - ) -> Union[str, Tuple[int, str, str]]: ... + with_extended_output: Literal[False] = False, + stdout_as_string: Literal[True] = True, + with_stdout: Literal[True] = True, + **subprocess_kwargs: Any, + ) -> str: ... @overload def execute( self, command: Union[str, Sequence[Any]], + istream: Union[None, int, BinaryIO] = None, *, as_process: Literal[False] = False, - stdout_as_string: Literal[False] = False, - ) -> Union[bytes, Tuple[int, bytes, str]]: ... + with_extended_output: Literal[False] = False, + stdout_as_string: Literal[False], + universal_newlines: Literal[False] = False, + with_stdout: Literal[True] = True, + **subprocess_kwargs: Any, + ) -> bytes: ... @overload def execute( self, command: Union[str, Sequence[Any]], + istream: Union[None, int, BinaryIO] = None, *, - with_extended_output: Literal[False], - as_process: Literal[False], - stdout_as_string: Literal[True], - ) -> str: ... + as_process: Literal[False] = False, + with_extended_output: Literal[True], + stdout_as_string: Literal[True] = True, + with_stdout: Literal[True] = True, + **subprocess_kwargs: Any, + ) -> Tuple[int, str, str]: ... @overload def execute( self, command: Union[str, Sequence[Any]], + istream: Union[None, int, BinaryIO] = None, *, - with_extended_output: Literal[False], - as_process: Literal[False], + as_process: Literal[False] = False, + with_extended_output: Literal[True], stdout_as_string: Literal[False], - ) -> bytes: ... + universal_newlines: Literal[False] = False, + with_stdout: Literal[True] = True, + **subprocess_kwargs: Any, + ) -> Tuple[int, bytes, str]: ... + + @overload + def execute( + self, + command: Union[str, Sequence[Any]], + istream: Union[None, int, BinaryIO] = None, + *, + as_process: Literal[False] = False, + with_extended_output: Literal[True], + **subprocess_kwargs: Any, + ) -> Tuple[int, Union[str, bytes, None], str]: ... + + @overload + def execute( + self, + command: Union[str, Sequence[Any]], + istream: Union[None, int, BinaryIO] = None, + *, + as_process: Literal[False] = False, + with_extended_output: Literal[False] = False, + **subprocess_kwargs: Any, + ) -> Union[str, bytes, None]: ... + + @overload + def execute( + self, + command: Union[str, Sequence[Any]], + istream: Union[None, int, BinaryIO] = None, + with_extended_output: bool = False, + with_exceptions: bool = True, + as_process: bool = False, + output_stream: Union[None, BinaryIO] = None, + stdout_as_string: bool = True, + kill_after_timeout: Union[None, float] = None, + with_stdout: bool = True, + universal_newlines: bool = False, + shell: Union[None, bool] = None, + env: Union[None, Mapping[str, str]] = None, + max_chunk_size: int = io.DEFAULT_BUFFER_SIZE, + strip_newline_in_stdout: bool = True, + **subprocess_kwargs: Any, + ) -> Union[None, str, bytes, Tuple[int, Union[str, bytes, None], str], AutoInterrupt]: ... def execute( self, command: Union[str, Sequence[Any]], - istream: Union[None, BinaryIO] = None, + istream: Union[None, int, BinaryIO] = None, with_extended_output: bool = False, with_exceptions: bool = True, as_process: bool = False, @@ -1239,7 +1297,7 @@ def execute( max_chunk_size: int = io.DEFAULT_BUFFER_SIZE, strip_newline_in_stdout: bool = True, **subprocess_kwargs: Any, - ) -> Union[str, bytes, Tuple[int, Union[str, bytes], str], AutoInterrupt]: + ) -> Union[None, str, bytes, Tuple[int, Union[str, bytes, None], str], AutoInterrupt]: R"""Handle executing the command, and consume and return the returned information (stdout). @@ -1303,9 +1361,9 @@ def execute( carefully considered, due to the following limitations: 1. This feature is not supported at all on Windows. - 2. Effectiveness may vary by operating system. ``ps --ppid`` is used to - enumerate child processes, which is available on most GNU/Linux systems - but not most others. + 2. Enumerating child processes requires ``pgrep -P``, or a ``ps`` command + supporting the POSIX ``-A`` and ``-o`` options if ``pgrep`` is not + installed. Effectiveness may vary on systems without these commands. 3. Deeper descendants do not receive signals, though they may sometimes terminate as a consequence of their parent processes being killed. 4. `kill_after_timeout` uses ``SIGKILL``, which can have negative side @@ -1465,14 +1523,24 @@ def kill_process(pid: int) -> None: This callback implementation would be ineffective and unsafe on Windows. """ - p = Popen(["ps", "--ppid", str(pid)], stdout=PIPE) child_pids = [] - if p.stdout is not None: - for line in p.stdout: - if len(line.split()) > 0: - local_pid = (line.split())[0] - if local_pid.isdigit(): - child_pids.append(int(local_pid)) + try: + p = Popen(["pgrep", "-P", str(pid)], stdout=PIPE) + except FileNotFoundError: + # POSIX ps does not support selecting by parent PID. + with Popen(["ps", "-A", "-o", "pid=", "-o", "ppid="], stdout=PIPE) as p: + if p.stdout is not None: + for line in p.stdout: + fields = line.split() + if len(fields) == 2 and all(field.isdigit() for field in fields): + if int(fields[1]) == pid: + child_pids.append(int(fields[0])) + else: + with p: + if p.stdout is not None: + for line in p.stdout: + if line.strip().isdigit(): + child_pids.append(int(line)) try: os.kill(pid, signal.SIGKILL) for child_pid in child_pids: @@ -1493,7 +1561,7 @@ def make_timeout_error() -> Union[str, bytes]: err = f'Timeout: the command "{" ".join(redacted_command)}" did not complete in {timeout:g} secs.' return err if universal_newlines else err.encode(defenc) - def communicate() -> Tuple[AnyStr, AnyStr]: + def communicate() -> Tuple[Union[str, bytes, None], Union[str, bytes, None]]: assert watchdog is not None assert kill_check is not None watchdog.start() @@ -1513,8 +1581,8 @@ def communicate() -> Tuple[AnyStr, AnyStr]: # Wait for the process to return. status = 0 - stdout_value: Union[str, bytes] = b"" - stderr_value: Union[str, bytes] = b"" + stdout_value: Union[str, bytes, None] = b"" + stderr_value: Union[str, bytes, None] = b"" newline = "\n" if universal_newlines else b"\n" try: if output_stream is None: @@ -1556,7 +1624,7 @@ def communicate() -> Tuple[AnyStr, AnyStr]: if self.GIT_PYTHON_TRACE == "full": cmdstr = " ".join(redacted_command) - def as_text(stdout_value: Union[bytes, str]) -> str: + def as_text(stdout_value: Union[bytes, str, None]) -> str: return not output_stream and safe_decode(stdout_value) or "" # END as_text @@ -1581,6 +1649,8 @@ def as_text(stdout_value: Union[bytes, str]) -> str: if isinstance(stdout_value, bytes) and stdout_as_string: # Could also be output_stream. stdout_value = safe_decode(stdout_value) + # stderr is always captured through PIPE. + assert stderr_value is not None # Allow access to the command's status code. if with_extended_output: return (status, stdout_value, safe_decode(stderr_value)) @@ -1819,7 +1889,7 @@ def _parse_object_header(self, header_line: str) -> Tuple[str, str, int]: raise ValueError("Failed to parse header: %r" % header_line) return (tokens[0], tokens[1], int(tokens[2])) - def _prepare_ref(self, ref: AnyStr) -> bytes: + def _prepare_ref(self, ref: object) -> bytes: # Required for command to separate refs on stdin, as bytes. if isinstance(ref, bytes): # Assume 40 bytes hexsha - bin-to-ascii for some reason returns bytes, not text. @@ -1846,7 +1916,7 @@ def _get_persistent_cmd(self, attr_name: str, cmd_name: str, *args: Any, **kwarg cmd = cast("Git.AutoInterrupt", cmd) return cmd - def __get_object_header(self, cmd: "Git.AutoInterrupt", ref: AnyStr) -> Tuple[str, str, int]: + def __get_object_header(self, cmd: "Git.AutoInterrupt", ref: Union[str, bytes]) -> Tuple[str, str, int]: if cmd.stdin and cmd.stdout: cmd.stdin.write(self._prepare_ref(ref)) cmd.stdin.flush() @@ -1854,7 +1924,7 @@ def __get_object_header(self, cmd: "Git.AutoInterrupt", ref: AnyStr) -> Tuple[st else: raise ValueError("cmd stdin was empty") - def get_object_header(self, ref: str) -> Tuple[str, str, int]: + def get_object_header(self, ref: Union[str, bytes]) -> Tuple[str, str, int]: """Use this method to quickly examine the type and size of the object behind the given ref. @@ -1868,7 +1938,7 @@ def get_object_header(self, ref: str) -> Tuple[str, str, int]: cmd = self._get_persistent_cmd("cat_file_header", "cat_file", batch_check=True) return self.__get_object_header(cmd, ref) - def get_object_data(self, ref: str) -> Tuple[str, str, int, bytes]: + def get_object_data(self, ref: Union[str, bytes]) -> Tuple[str, str, int, bytes]: """Similar to :meth:`get_object_header`, but returns object data as well. :return: @@ -1882,7 +1952,7 @@ def get_object_data(self, ref: str) -> Tuple[str, str, int, bytes]: del stream return (hexsha, typename, size, data) - def stream_object_data(self, ref: str) -> Tuple[str, str, int, "Git.CatFileContentStream"]: + def stream_object_data(self, ref: Union[str, bytes]) -> Tuple[str, str, int, "Git.CatFileContentStream"]: """Similar to :meth:`get_object_data`, but returns the data as a stream. :return: diff --git a/git/index/base.py b/git/index/base.py index a3c915242..560fc5e2c 100644 --- a/git/index/base.py +++ b/git/index/base.py @@ -1235,7 +1235,7 @@ def _read_commit_editmsg(self) -> str: def _commit_editmsg_filepath(self) -> str: return osp.join(self.repo.common_dir, "COMMIT_EDITMSG") - def _flush_stdin_and_wait(cls, proc: "Popen[bytes]", ignore_stdout: bool = False) -> bytes: + def _flush_stdin_and_wait(self, proc: "Popen[bytes]", ignore_stdout: bool = False) -> bytes: stdin_IO = proc.stdin if stdin_IO: stdin_IO.flush() diff --git a/git/index/fun.py b/git/index/fun.py index 886e10de9..45c18ace4 100644 --- a/git/index/fun.py +++ b/git/index/fun.py @@ -46,7 +46,7 @@ from git.types import PathLike if TYPE_CHECKING: - from git.db import GitCmdObjectDB + from gitdb.db.base import ObjectDBR, ObjectDBW from git.objects.tree import TreeCacheTup from .base import IndexFile @@ -412,7 +412,7 @@ def read_cache( def write_tree_from_cache( - entries: List[IndexEntry], odb: "GitCmdObjectDB", sl: slice, si: int = 0 + entries: List[IndexEntry], odb: "ObjectDBW", sl: slice, si: int = 0 ) -> Tuple[bytes, List["TreeCacheTup"]]: R"""Create a tree from the given sorted list of entries and put the respective trees into the given object database. @@ -484,7 +484,7 @@ def _tree_entry_to_baseindexentry(tree_entry: "TreeCacheTup", stage: int) -> Bas return BaseIndexEntry((tree_entry[1], tree_entry[0], stage << CE_STAGESHIFT, tree_entry[2])) -def aggressive_tree_merge(odb: "GitCmdObjectDB", tree_shas: Sequence[bytes]) -> List[BaseIndexEntry]: +def aggressive_tree_merge(odb: "ObjectDBR", tree_shas: Sequence[bytes]) -> List[BaseIndexEntry]: R""" :return: List of :class:`~git.index.typ.BaseIndexEntry`\s representing the aggressive diff --git a/git/index/typ.py b/git/index/typ.py index 927633a9f..78f7c8ea5 100644 --- a/git/index/typ.py +++ b/git/index/typ.py @@ -9,12 +9,13 @@ from pathlib import Path from git.objects import Blob +from git.objects.base import IndexObject from .util import pack, unpack # typing ---------------------------------------------------------------------- -from typing import NamedTuple, Sequence, TYPE_CHECKING, Tuple, Union, cast +from typing import NamedTuple, Sequence, TYPE_CHECKING, Tuple, Type, TypeVar, Union, cast from git.types import PathLike @@ -22,6 +23,7 @@ from git.repo import Repo StageType = int +_T_IndexEntry = TypeVar("_T_IndexEntry", bound="BaseIndexEntry") # --------------------------------------------------------------------------------- @@ -104,15 +106,20 @@ class BaseIndexEntry(BaseIndexEntryHelper): """ def __new__( - cls, + cls: Type[_T_IndexEntry], inp_tuple: Union[ Tuple[int, bytes, int, PathLike], + Tuple[int, bytes, int, PathLike, bytes, bytes, int, int, int, int, int], Tuple[int, bytes, int, PathLike, bytes, bytes, int, int, int, int, int, int], ], - ) -> "BaseIndexEntry": + ) -> _T_IndexEntry: """Override ``__new__`` to allow construction from a tuple for backwards compatibility.""" - return super().__new__(cls, *inp_tuple) + if len(inp_tuple) == 4: + return BaseIndexEntryHelper.__new__(cls, *inp_tuple) + if len(inp_tuple) == 11: + return BaseIndexEntryHelper.__new__(cls, *inp_tuple) + return BaseIndexEntryHelper.__new__(cls, *inp_tuple) def __str__(self) -> str: return "%o %s %i\t%s" % (self.mode, self.hexsha, self.stage, self.path) @@ -148,7 +155,7 @@ def intent_to_add(self) -> bool: return (self.extended_flags & CE_EXT_INTENT_TO_ADD) > 0 @classmethod - def from_blob(cls, blob: Blob, stage: int = 0) -> "BaseIndexEntry": + def from_blob(cls, blob: IndexObject, stage: int = 0) -> "BaseIndexEntry": """:return: Fully equipped BaseIndexEntry at the given stage""" return cls((blob.mode, blob.binsha, stage << CE_STAGESHIFT, blob.path)) @@ -192,10 +199,10 @@ def from_base(cls, base: "BaseIndexEntry") -> "IndexEntry": Instance of type :class:`BaseIndexEntry`. """ time = pack(">LL", 0, 0) - return IndexEntry((base.mode, base.binsha, base.flags, base.path, time, time, 0, 0, 0, 0, 0)) # type: ignore[arg-type] + return IndexEntry((base.mode, base.binsha, base.flags, base.path, time, time, 0, 0, 0, 0, 0)) @classmethod - def from_blob(cls, blob: Blob, stage: int = 0) -> "IndexEntry": + def from_blob(cls, blob: IndexObject, stage: int = 0) -> "IndexEntry": """:return: Minimal entry resembling the given blob object""" time = pack(">LL", 0, 0) return IndexEntry( @@ -211,5 +218,5 @@ def from_blob(cls, blob: Blob, stage: int = 0) -> "IndexEntry": 0, 0, blob.size, - ) # type: ignore[arg-type] + ) ) diff --git a/git/objects/base.py b/git/objects/base.py index faf600c6b..1188ec0c9 100644 --- a/git/objects/base.py +++ b/git/objects/base.py @@ -18,7 +18,7 @@ from typing import Any, TYPE_CHECKING, Union -from git.types import AnyGitObject, GitObjectTypeString, PathLike +from git.types import AnyGitObject, GitObjectTypeString, PathLike, SupportsWrite if TYPE_CHECKING: from gitdb.base import OStream @@ -200,7 +200,7 @@ def data_stream(self) -> "OStream": """ return self.repo.odb.stream(self.binsha) - def stream_data(self, ostream: "OStream") -> "Object": + def stream_data(self, ostream: SupportsWrite[bytes]) -> "Object": """Write our data directly to the given output stream. :param ostream: diff --git a/git/objects/commit.py b/git/objects/commit.py index 45843eac2..0348d3299 100644 --- a/git/objects/commit.py +++ b/git/objects/commit.py @@ -502,7 +502,7 @@ def _interpret_trailers( ) -> str: message_bytes = message if isinstance(message, bytes) else message.encode(encoding, errors="strict") cmd = [repo.git.GIT_PYTHON_GIT_EXECUTABLE, "interpret-trailers", *trailer_args] - proc: Git.AutoInterrupt = repo.git.execute( # type: ignore[call-overload] + proc: Git.AutoInterrupt = repo.git.execute( cmd, as_process=True, istream=PIPE, diff --git a/git/objects/fun.py b/git/objects/fun.py index ad5fbd59b..7ef229990 100644 --- a/git/objects/fun.py +++ b/git/objects/fun.py @@ -30,7 +30,7 @@ if TYPE_CHECKING: from _typeshed import ReadableBuffer - from git import GitCmdObjectDB + from gitdb.db.base import ObjectDBR EntryTup = Tuple[bytes, int, str] # Same as TreeCacheTup in tree.py. EntryTupOrNone = Union[EntryTup, None] @@ -166,7 +166,7 @@ def _to_full_path(item: EntryTupOrNone, path_prefix: str) -> EntryTupOrNone: def traverse_trees_recursive( - odb: "GitCmdObjectDB", tree_shas: Sequence[Union[bytes, None]], path_prefix: str + odb: "ObjectDBR", tree_shas: Sequence[Union[bytes, None]], path_prefix: str ) -> List[Tuple[EntryTupOrNone, ...]]: """ :return: @@ -253,7 +253,7 @@ def traverse_trees_recursive( return out -def traverse_tree_recursive(odb: "GitCmdObjectDB", tree_sha: bytes, path_prefix: str) -> List[EntryTup]: +def traverse_tree_recursive(odb: "ObjectDBR", tree_sha: bytes, path_prefix: str) -> List[EntryTup]: """ :return: List of entries of the tree pointed to by the binary `tree_sha`. diff --git a/git/objects/submodule/base.py b/git/objects/submodule/base.py index 8308e4459..ba281c499 100644 --- a/git/objects/submodule/base.py +++ b/git/objects/submodule/base.py @@ -14,7 +14,7 @@ import stat import sys import uuid -import urllib +import urllib.parse import git from git.cmd import Git @@ -1769,7 +1769,7 @@ def iter_items( # END handle critical error # Make sure we are looking at a submodule object. - if type(sm) is not git.objects.submodule.base.Submodule: + if type(sm) is not Submodule: continue # Fill in remaining info - saves time as it doesn't have to be parsed again. diff --git a/git/objects/tag.py b/git/objects/tag.py index 88671d316..18b4a9ca4 100644 --- a/git/objects/tag.py +++ b/git/objects/tag.py @@ -23,6 +23,8 @@ from typing import List, TYPE_CHECKING, Union +from git.types import AnyGitObject + if sys.version_info >= (3, 8): from typing import Literal else: @@ -61,7 +63,7 @@ def __init__( self, repo: "Repo", binsha: bytes, - object: Union[None, base.Object] = None, + object: Union[None, AnyGitObject] = None, tag: Union[None, str] = None, tagger: Union[None, Actor] = None, tagged_date: Union[int, None] = None, diff --git a/git/refs/reference.py b/git/refs/reference.py index 0c4327225..7d6c62cf5 100644 --- a/git/refs/reference.py +++ b/git/refs/reference.py @@ -26,7 +26,7 @@ def require_remote_ref_path(func: Callable[..., _T]) -> Callable[..., _T]: """A decorator raising :exc:`ValueError` if we are not a valid remote, based on the path.""" - def wrapper(self: T_References, *args: Any) -> _T: + def wrapper(self: SymbolicReference, *args: Any) -> _T: if not self.is_remote(): raise ValueError("ref path does not point to a remote reference: %s" % self.path) return func(self, *args) diff --git a/git/remote.py b/git/remote.py index e2d5cbc1d..2ddf11af0 100644 --- a/git/remote.py +++ b/git/remote.py @@ -38,6 +38,7 @@ Sequence, TYPE_CHECKING, Type, + TypeVar, Union, cast, overload, @@ -50,6 +51,8 @@ from git.objects.submodule.base import UpdateProgress from git.repo.base import Repo +_T_RemoteName = TypeVar("_T_RemoteName", bound=Union[str, "Remote"]) + flagKeyLiteral = Literal[" ", "!", "+", "-", "*", "=", "t", "?"] # ------------------------------------------------------------- @@ -820,19 +823,20 @@ def add(cls, repo: "Repo", name: str, url: str, **kwargs: Any) -> "Remote": return cls.create(repo, name, url, **kwargs) @classmethod - def remove(cls, repo: "Repo", name: str) -> str: + def remove(cls, repo: "Repo", name: _T_RemoteName) -> _T_RemoteName: """Remove the remote with the given name. :return: The passed remote name to remove """ repo.git.remote("rm", name) - if isinstance(name, cls): - name._clear_cache() + remote = name + if isinstance(remote, cls): + remote._clear_cache() return name @classmethod - def rm(cls, repo: "Repo", name: str) -> str: + def rm(cls, repo: "Repo", name: _T_RemoteName) -> _T_RemoteName: """Alias of remove. Remove the remote with the given name. @@ -901,7 +905,7 @@ def _get_fetch_info_from_stderr( kill_after_timeout=kill_after_timeout, ) - stderr_text = progress.error_lines and "\n".join(progress.error_lines) or "" + stderr_text = "\n".join(progress.error_lines) proc.wait(stderr=stderr_text) if stderr_text: _logger.warning("Error lines received while fetching: %s", stderr_text) @@ -973,7 +977,7 @@ def stdout_handler(line: str) -> None: decode_streams=False, kill_after_timeout=kill_after_timeout, ) - stderr_text = progress.error_lines and "\n".join(progress.error_lines) or "" + stderr_text = "\n".join(progress.error_lines) try: proc.wait(stderr=stderr_text) except Exception as e: diff --git a/git/repo/base.py b/git/repo/base.py index f326266d6..29922e118 100644 --- a/git/repo/base.py +++ b/git/repo/base.py @@ -18,6 +18,7 @@ import warnings import gitdb +import gitdb.util from gitdb.db.loose import LooseObjectDB from gitdb.exc import BadObject @@ -33,7 +34,7 @@ from git.index import IndexFile from git.objects import Submodule, RootModule, Commit from git.refs import HEAD, Head, Reference, TagReference -from git.remote import Remote, add_progress, to_progress_instance +from git.remote import Remote, _T_RemoteName, add_progress, to_progress_instance from git.util import ( Actor, cygpath, @@ -95,7 +96,7 @@ class BlameEntry(NamedTuple): - commit: Dict[str, Commit] + commit: Commit linenos: range orig_path: Optional[str] orig_linenos: range @@ -396,7 +397,7 @@ def __init__( self._working_tree_dir = None # END working dir handling - self.working_dir: PathLike = self._working_tree_dir or self.common_dir + self.working_dir = self._working_tree_dir or self.common_dir self.git = self.GitCommandWrapperType(self.working_dir) if common_dir_env is not None: self.git.update_environment(GIT_DIR=os.fspath(self.git_dir), GIT_COMMON_DIR=os.fspath(self.common_dir)) @@ -718,7 +719,7 @@ def create_remote(self, name: str, url: str, **kwargs: Any) -> Remote: """ return Remote.create(self, name, url, **kwargs) - def delete_remote(self, remote: "Remote") -> str: + def delete_remote(self, remote: _T_RemoteName) -> _T_RemoteName: """Delete the given remote.""" return Remote.remove(self, remote) @@ -1504,7 +1505,7 @@ def _clone( git: "Git", url: PathLike, path: PathLike, - odb_default_type: Type[GitCmdObjectDB], + odb_default_type: Type[LooseObjectDB], progress: Union["RemoteProgress", "UpdateProgress", Callable[..., "RemoteProgress"], None] = None, multi_options: Optional[List[str]] = None, allow_unsafe_protocols: bool = False, @@ -1711,7 +1712,7 @@ def clone_from( def archive( self, ostream: Union[TextIO, BinaryIO], - treeish: Optional[str] = None, + treeish: Union[str, Commit, None] = None, prefix: Optional[str] = None, allow_unsafe_options: bool = False, allow_unsafe_protocols: bool = False, diff --git a/git/repo/fun.py b/git/repo/fun.py index eb0d8075a..a565054bc 100644 --- a/git/repo/fun.py +++ b/git/repo/fun.py @@ -40,11 +40,10 @@ from git.types import AnyGitObject, Literal, PathLike if TYPE_CHECKING: - from git.db import GitCmdObjectDB + from gitdb.db import CompoundDB, LooseObjectDB from git.objects import Commit from git.refs.reference import Reference from git.refs.log import RefLog, RefLogEntry - from git.refs.tag import Tag from .base import Repo @@ -158,7 +157,7 @@ def find_submodule_git_dir(d: PathLike) -> Optional[PathLike]: return path if is_git_dir(path) else None -def short_to_long(odb: "GitCmdObjectDB", hexsha: str) -> Optional[bytes]: +def short_to_long(odb: Union["CompoundDB", "LooseObjectDB"], hexsha: str) -> Optional[bytes]: """ :return: Long hexadecimal sha1 from the given less than 40 byte hexsha, or ``None`` if no @@ -261,7 +260,7 @@ def name_to_object(repo: "Repo", name: str, return_ref: bool = False) -> Union[A return Object.new_from_sha(repo, hex_to_bin(hexsha)) -def deref_tag(tag: "Tag") -> AnyGitObject: +def deref_tag(tag: AnyGitObject) -> AnyGitObject: """Recursively dereference a tag and return the resulting object.""" while True: try: @@ -272,7 +271,7 @@ def deref_tag(tag: "Tag") -> AnyGitObject: return tag -def to_commit(obj: Object) -> "Commit": +def to_commit(obj: AnyGitObject) -> "Commit": """Convert the given object to a commit if possible and return it.""" if obj.type == "tag": obj = deref_tag(obj) @@ -521,7 +520,7 @@ def _find_commit_by_message( if rev is None: commits = _all_ref_commits(repo) else: - commits = _reachable_commits([to_commit(cast(Object, rev))]) + commits = _reachable_commits([to_commit(rev)]) # END handle starting point for commit in commits: @@ -541,7 +540,7 @@ def _all_ref_commits(repo: "Repo") -> Iterator["Commit"]: starts = [] for ref in repo.references: try: - starts.append(to_commit(cast(Object, ref.object))) + starts.append(to_commit(ref.object)) except (BadName, ValueError): pass # END skip refs that do not point to commits @@ -589,7 +588,7 @@ def _index_lookup(repo: "Repo", spec: str) -> AnyGitObject: def _tree_lookup(obj: AnyGitObject, path: str) -> AnyGitObject: if obj.type != "tree": - obj = to_commit(cast(Object, obj)).tree + obj = to_commit(obj).tree # END get tree if not path: return obj @@ -604,9 +603,9 @@ def _peel(obj: AnyGitObject, output_type: str, repo: "Repo", rev: str) -> AnyGit if output_type == "object": return obj if output_type == "commit": - return to_commit(cast(Object, obj)) + return to_commit(obj) if output_type == "tree": - return to_commit(cast(Object, obj)).tree if obj.type != "tree" else obj + return to_commit(obj).tree if obj.type != "tree" else obj if output_type == "blob": obj = deref_tag(obj) if obj.type == "tag" else obj if obj.type == output_type: diff --git a/git/types.py b/git/types.py index 100fff43f..31d40bf3b 100644 --- a/git/types.py +++ b/git/types.py @@ -48,6 +48,22 @@ _T = TypeVar("_T") """Type variable used internally in GitPython.""" +_T_Stream_co = TypeVar("_T_Stream_co", str, bytes, covariant=True) +_T_Stream_contra = TypeVar("_T_Stream_contra", str, bytes, contravariant=True) + + +class SupportsRead(Protocol[_T_Stream_co]): + """A stream supporting reads, without requiring the full IO interface.""" + + def read(self, __size: int = -1) -> _T_Stream_co: ... + + +class SupportsWrite(Protocol[_T_Stream_contra]): + """A stream supporting writes, including writers that return None.""" + + def write(self, __data: _T_Stream_contra) -> object: ... + + AnyGitObject = Union["Commit", "Tree", "TagObject", "Blob"] """Union of the :class:`~git.objects.base.Object`-based types that represent actual git object types. diff --git a/git/util.py b/git/util.py index a80e667c7..f72e3d7c1 100644 --- a/git/util.py +++ b/git/util.py @@ -68,7 +68,6 @@ from typing import ( Any, AnyStr, - BinaryIO, Callable, Dict, Generator, @@ -101,6 +100,8 @@ PathLike, Protocol, SupportsIndex, + SupportsRead, + SupportsWrite, Total_TD, runtime_checkable, ) @@ -253,7 +254,7 @@ def rmfile(path: PathLike) -> None: os.remove(path) -def stream_copy(source: BinaryIO, destination: BinaryIO, chunk_size: int = 512 * 1024) -> int: +def stream_copy(source: SupportsRead[AnyStr], destination: SupportsWrite[AnyStr], chunk_size: int = 512 * 1024) -> int: """Copy all data from the `source` stream into the `destination` stream in chunks of size `chunk_size`. diff --git a/pyproject.toml b/pyproject.toml index b7c437bf3..fbcde3611 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,7 +19,7 @@ testpaths = "test" # Space separated list of paths from root e.g test tests doc # filterwarnings ignore::WarningType # ignores those warnings [tool.mypy] -files = ["git/", "test/deprecation/"] +files = ["git/", "test/deprecation/", "test/test_typing.py"] disallow_untyped_defs = true no_implicit_optional = true warn_redundant_casts = true @@ -40,6 +40,8 @@ pythonVersion = "3.7" include = [ "git", "test/deprecation", + "test/test_typing.py", + "test/test_git.py", ] extraPaths = [ "gitdb", diff --git a/test-requirements.txt b/test-requirements.txt index e2443825c..0fabac809 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1,7 +1,7 @@ coverage[toml] basedpyright==1.39.9 ; python_version >= "3.9" and sys_platform != "cygwin" ddt >= 1.1.1, != 1.4.3 -mock ; python_version < "3.8" +mock # Also needed to typecheck the Python 3.7 import branches. mypy==1.18.2 ; python_version >= "3.9" # pin mypy version to avoid new errors pre-commit pytest >= 7.3.1 diff --git a/test/test_git.py b/test/test_git.py index a88d980fb..14fc7dfc3 100644 --- a/test/test_git.py +++ b/test/test_git.py @@ -14,6 +14,7 @@ import pickle import re import shutil +import signal import subprocess import sys import tempfile @@ -136,24 +137,24 @@ def test_it_raises_errors(self): self.assertRaises(GitCommandError, self.git.this_does_not_exist) def test_it_transforms_kwargs_into_git_command_arguments(self): - self.assertEqual(["-s"], self.git.transform_kwargs(**{"s": True})) - self.assertEqual(["-s", "5"], self.git.transform_kwargs(**{"s": 5})) - self.assertEqual([], self.git.transform_kwargs(**{"s": None})) + self.assertEqual(["-s"], self.git.transform_kwargs(s=True)) + self.assertEqual(["-s", "5"], self.git.transform_kwargs(s=5)) + self.assertEqual([], self.git.transform_kwargs(s=None)) - self.assertEqual(["--max-count"], self.git.transform_kwargs(**{"max_count": True})) - self.assertEqual(["--max-count=5"], self.git.transform_kwargs(**{"max_count": 5})) - self.assertEqual(["--max-count=0"], self.git.transform_kwargs(**{"max_count": 0})) - self.assertEqual([], self.git.transform_kwargs(**{"max_count": None})) + self.assertEqual(["--max-count"], self.git.transform_kwargs(max_count=True)) + self.assertEqual(["--max-count=5"], self.git.transform_kwargs(max_count=5)) + self.assertEqual(["--max-count=0"], self.git.transform_kwargs(max_count=0)) + self.assertEqual([], self.git.transform_kwargs(max_count=None)) # Multiple args are supported by using lists/tuples. self.assertEqual( ["-L", "1-3", "-L", "12-18"], - self.git.transform_kwargs(**{"L": ("1-3", "12-18")}), + self.git.transform_kwargs(L=("1-3", "12-18")), ) - self.assertEqual(["-C", "-C"], self.git.transform_kwargs(**{"C": [True, True, None, False]})) + self.assertEqual(["-C", "-C"], self.git.transform_kwargs(C=[True, True, None, False])) # Order is undefined. - res = self.git.transform_kwargs(**{"s": True, "t": True}) + res = self.git.transform_kwargs(s=True, t=True) self.assertEqual({"-s", "-t"}, set(res)) def test_check_unsafe_options_normalizes_kwargs(self): @@ -220,7 +221,9 @@ def test_option_candidates_include_falsey_non_boolean_values(self): candidates = Git._option_candidates(kwargs=kwargs) self.assertEqual(candidates, ["--pathspec-from-file"]) - self.assertEqual(self.git.transform_kwargs(**kwargs), ["--pathspec-from-file=0"]) + self.assertEqual( + self.git.transform_kwargs(split_single_char_options=True, **kwargs), ["--pathspec-from-file=0"] + ) with self.assertRaises(UnsafeOptionError): Git.check_unsafe_options( options=candidates, @@ -332,6 +335,63 @@ def test_it_honors_kill_after_timeout_with_output_stream(self): self.assertEqual(output_stream.getvalue(), b"started\n") self.assertIn("Timeout: the command", stderr) + @skipUnless( + sys.platform not in ("win32", "cygwin"), + "child process lookup requires pgrep or POSIX ps", + ) + @ddt.data(False, True) + def test_timeout_kills_direct_child(self, without_pgrep): + with tempfile.TemporaryDirectory() as directory: + marker = Path(directory, "child-survived") + child_code = ( + "import pathlib, sys, time; time.sleep(2); " + "pathlib.Path(sys.argv[1]).write_text('survived', encoding='utf-8')" + ) + parent_code = ( + "import subprocess, sys, time; " + "subprocess.Popen([sys.executable, '-c', sys.argv[1], sys.argv[2]]); " + "time.sleep(30)" + ) + popen = cmd.Popen + + def portable_popen(args, **kwargs): + if without_pgrep and args[0] == "pgrep": + raise FileNotFoundError("pgrep is not installed") + return popen(args, **kwargs) + + with mock.patch.object(cmd, "Popen", side_effect=portable_popen): + status, _, stderr = self.git.execute( + [sys.executable, "-c", parent_code, child_code, str(marker)], + kill_after_timeout=1, + with_exceptions=False, + with_extended_output=True, + ) + + self.assertNotEqual(status, 0) + self.assertIn("Timeout: the command", stderr) + self.assertFalse(marker.exists(), "the direct child survived the timeout") + + @skipUnless(sys.platform != "win32", "kill_after_timeout is not supported on Windows") + def test_timeout_ps_fallback_selects_only_direct_children(self): + process = mock.MagicMock() + process.pid = 1234 + process.communicate.return_value = (b"", b"") + process.returncode = -signal.SIGKILL + ps = mock.MagicMock() + ps.__enter__.return_value = ps + ps.stdout = io.BytesIO(b"PID PPID\n 321 1\n 5678 1234\n 9012 5678\n\n") + + with contextlib.ExitStack() as stack: + stack.enter_context(mock.patch.object(cmd, "safer_popen", return_value=process)) + stack.enter_context(mock.patch.object(cmd, "Popen", side_effect=[FileNotFoundError, ps])) + kill = stack.enter_context(mock.patch.object(cmd.os, "kill")) + timer = stack.enter_context(mock.patch.object(cmd.threading, "Timer")) + # Run the timeout callback synchronously, with no real processes or signals. + timer.return_value.start.side_effect = lambda: timer.call_args.args[1](1234) + self.git.execute(["git", "version"], kill_after_timeout=1, with_exceptions=False) + + self.assertEqual(kill.call_args_list, [mock.call(1234, signal.SIGKILL), mock.call(5678, signal.SIGKILL)]) + def test_it_executes_git_without_stdout_redirect(self): returncode, stdout, stderr = self.git.execute( ["git", "version"], @@ -576,6 +636,7 @@ def test_initial_refresh_from_bad_git_path_env_warn(self, case): with mock.patch.dict(os.environ, env_vars): with self.assertLogs(cmd.__name__, logging.CRITICAL) as ctx: refresh() + assert ctx is not None self.assertEqual(len(ctx.records), 1) message = ctx.records[0].getMessage() self.assertRegex(message, r"\ABad git executable.\n") @@ -695,6 +756,7 @@ def test_refresh_with_good_absolute_git_path_arg(self): def test_refresh_with_good_relative_git_path_arg(self): """Good relative path arg is resolved to absolute path and set.""" absolute_path = shutil.which("git") + assert absolute_path is not None dirname, basename = osp.split(absolute_path) with cwd(dirname): diff --git a/test/test_typing.py b/test/test_typing.py new file mode 100644 index 000000000..101c05520 --- /dev/null +++ b/test/test_typing.py @@ -0,0 +1,91 @@ +# This module is part of GitPython and is released under the +# 3-Clause BSD License: https://opensource.org/license/bsd-3-clause/ + +"""Runtime and static checks for public APIs with related input/output types.""" + +from io import BytesIO, StringIO +import subprocess +import sys +from typing import List, Tuple, TYPE_CHECKING + +import pytest + +from git import Git +from git.exc import GitCommandError +from git.index.typ import BaseIndexEntry, IndexEntry +from git.util import stream_copy + + +def test_index_entry_constructor_shapes() -> None: + class DerivedEntry(IndexEntry): + pass + + short = (0o100644, b"\0" * 20, 0, "file") + full = short + (b"\0" * 8, b"\0" * 8, 1, 2, 3, 4, 5) + entries: List[IndexEntry] = [IndexEntry(short), IndexEntry(full), IndexEntry(full + (0x4000,))] + derived: DerivedEntry = DerivedEntry(short) + + assert all(type(entry) is IndexEntry for entry in entries) + assert [entry.size for entry in entries] == [0, 5, 5] + assert [entry.skip_worktree for entry in entries] == [False, False, True] + assert type(derived) is DerivedEntry + assert IndexEntry.from_base(BaseIndexEntry(short)) == entries[0] + + +def test_stream_copy_minimal_writer() -> None: + class Writer: + def __init__(self) -> None: + self.data = b"" + + def write(self, data: bytes) -> None: + self.data += data + + writer = Writer() + assert stream_copy(BytesIO(b"payload"), writer, chunk_size=3) == 7 + assert writer.data == b"payload" + text = StringIO() + assert stream_copy(StringIO("payload"), text, chunk_size=3) == 7 + assert text.getvalue() == "payload" + + +def test_process_wait_with_no_previous_stderr() -> None: + process = Git().execute( + [sys.executable, "-c", "import sys; sys.stderr.write('failure'); sys.exit(1)"], + as_process=True, + istream=subprocess.DEVNULL, + shell=False, + ) + with pytest.raises(GitCommandError, match="failure"): + process.wait(stderr=None) + + +def test_execute_output_types() -> None: + git = Git() + command = [sys.executable, "-c", "print('payload', end='')"] + text: str = git.execute(command, with_exceptions=False, shell=False) + binary: bytes = git.execute(command, stdout_as_string=False, shell=False) + extended_text: Tuple[int, str, str] = git.execute(command, with_extended_output=True, shell=False) + extended_binary: Tuple[int, bytes, str] = git.execute( + command, with_extended_output=True, stdout_as_string=False, shell=False + ) + assert text == "payload" + assert binary == b"payload" + assert extended_text == (0, text, "") + assert extended_binary == (0, binary, "") + + +if TYPE_CHECKING: + from git import Remote, Repo + from git.repo.base import BlameEntry + from git.objects import Commit, Submodule + + repo = Repo() + remote = Remote(repo, "origin") + removed_names: List[str] = [Remote.remove(repo, "origin"), Remote.rm(repo, "origin"), repo.delete_remote("origin")] + removed_remotes: List[Remote] = [Remote.remove(repo, remote), Remote.rm(repo, remote), repo.delete_remote(remote)] + blame = BlameEntry(repo.head.commit, range(1), "file", range(1)) + commit: Commit = blame.commit + submodule_entry: IndexEntry = IndexEntry.from_blob(Submodule(repo, b"\0" * 20)) + Git().get_object_header(b"HEAD") + Git().get_object_data(b"HEAD") + Git().stream_object_data(b"HEAD")