diff --git a/scm.py b/scm.py index 21363d38c..97df0dce4 100644 --- a/scm.py +++ b/scm.py @@ -70,6 +70,13 @@ class GIT(object): results.append(('%s ' % m.group(1), m.group(2))) return results + @staticmethod + def GetEmail(repo_root): + """Retrieves the user email address if known.""" + # We could want to look at the svn cred when it has a svn remote but it + # should be fine for now, users should simply configure their git settings. + return GIT.Capture(['config', 'user.email'], repo_root).strip() + class SVN(object): COMMAND = "svn" @@ -413,3 +420,57 @@ class SVN(object): data += "@@ -0,0 +1,%d @@\n" % nb_lines data += ''.join(file_content) return data + + @staticmethod + def GetEmail(repo_root): + """Retrieves the svn account which we assume is an email address.""" + infos = SVN.CaptureInfo(repo_root) + uuid = infos.get('UUID') + root = infos.get('Repository Root') + if not root: + return None + + # Should check for uuid but it is incorrectly saved for https creds. + realm = root.rsplit('/', 1)[0] + if root.startswith('https') or not uuid: + regexp = re.compile(r'<%s:\d+>.*' % realm) + else: + regexp = re.compile(r'<%s:\d+> %s' % (realm, uuid)) + if regexp is None: + return None + if sys.platform.startswith('win'): + if not 'APPDATA' in os.environ: + return None + auth_dir = os.path.join(os.environ['APPDATA'], 'auth', 'svn.simple') + else: + if not 'HOME' in os.environ: + return None + auth_dir = os.path.join(os.environ['HOME'], '.subversion', 'auth', + 'svn.simple') + for credfile in os.listdir(auth_dir): + cred_info = SVN.ReadSimpleAuth(os.path.join(auth_dir, credfile)) + if regexp.match(cred_info.get('svn:realmstring')): + return cred_info.get('username') + + @staticmethod + def ReadSimpleAuth(filename): + f = open(filename, 'r') + values = {} + def ReadOneItem(type): + m = re.match(r'%s (\d+)' % type, f.readline()) + if not m: + return None + data = f.read(int(m.group(1))) + if f.read(1) != '\n': + return None + return data + + while True: + key = ReadOneItem('K') + if not key: + break + value = ReadOneItem('V') + if not value: + break + values[key] = value + return values diff --git a/tests/gclient_scm_test.py b/tests/gclient_scm_test.py index e62711a97..da7783b42 100755 --- a/tests/gclient_scm_test.py +++ b/tests/gclient_scm_test.py @@ -65,8 +65,9 @@ class SVNWrapperTestCase(BaseTestCase): def testDir(self): members = [ 'COMMAND', 'Capture', 'CaptureHeadRevision', 'CaptureInfo', - 'CaptureStatus', 'DiffItem', 'FullUrlForRelativeUrl', 'GetFileProperty', - 'IsMoved', 'Run', 'RunAndFilterOutput', 'RunAndGetFileList', + 'CaptureStatus', 'DiffItem', 'FullUrlForRelativeUrl', 'GetEmail', + 'GetFileProperty', 'IsMoved', 'ReadEntries', 'ReadSimpleAuth', 'Run', + 'RunAndFilterOutput', 'RunAndGetFileList', 'RunCommand', 'cleanup', 'diff', 'export', 'pack', 'relpath', 'revert', 'revinfo', 'runhooks', 'scm_name', 'status', 'update', 'url', ] @@ -367,6 +368,7 @@ from :3 def testDir(self): members = [ 'COMMAND', 'Capture', 'CaptureStatus', 'FullUrlForRelativeUrl', + 'GetEmail', 'RunCommand', 'cleanup', 'diff', 'export', 'relpath', 'revert', 'revinfo', 'runhooks', 'scm_name', 'status', 'update', 'url', ] diff --git a/tests/scm_unittest.py b/tests/scm_unittest.py index 4dbb92965..5e1c09fbc 100755 --- a/tests/scm_unittest.py +++ b/tests/scm_unittest.py @@ -106,6 +106,7 @@ from :3 self.relpath = '.' self.base_path = scm.os.path.join(self.root_dir, self.relpath) self.enabled = self.CreateGitRepo(self.sample_git_import, self.base_path) + self.fake_root = self.Dir() def tearDown(self): shutil.rmtree(self.root_dir) @@ -114,11 +115,17 @@ from :3 def testMembersChanged(self): self.mox.ReplayAll() members = [ - 'COMMAND', 'Capture', 'CaptureStatus', + 'COMMAND', 'Capture', 'CaptureStatus', 'GetEmail', ] # If this test fails, you should add the relevant test. self.compareMembers(scm.GIT, members) + def testGetEmail(self): + self.mox.StubOutWithMock(scm.GIT, 'Capture') + scm.GIT.Capture(['config', 'user.email'], self.fake_root).AndReturn('mini@me.com') + self.mox.ReplayAll() + self.assertEqual(scm.GIT.GetEmail(self.fake_root), 'mini@me.com') + class SVNTestCase(BaseSCMTestCase): def setUp(self): @@ -132,8 +139,9 @@ class SVNTestCase(BaseSCMTestCase): self.mox.ReplayAll() members = [ 'COMMAND', 'Capture', 'CaptureHeadRevision', 'CaptureInfo', - 'CaptureStatus', 'DiffItem', 'GetFileProperty', 'IsMoved', 'Run', - 'RunAndFilterOutput', 'RunAndGetFileList', + 'CaptureStatus', 'DiffItem', 'GetEmail', 'GetFileProperty', 'IsMoved', + 'ReadEntries', 'ReadSimpleAuth', 'Run', 'RunAndFilterOutput', + 'RunAndGetFileList', ] # If this test fails, you should add the relevant test. self.compareMembers(scm.SVN, members) diff --git a/tests/trychange_unittest.py b/tests/trychange_unittest.py index 828d3ea4e..938f23cd3 100644 --- a/tests/trychange_unittest.py +++ b/tests/trychange_unittest.py @@ -76,7 +76,7 @@ class GITUnittest(TryChangeTestsBase): def testMembersChanged(self): members = [ - 'GenerateDiff', 'GetEmail', 'GetFileNames', 'GetLocalRoot', + 'GenerateDiff', 'GetFileNames', 'GetLocalRoot', 'GetPatchName', 'ProcessOptions', 'options' ] # If this test fails, you should add the relevant test. diff --git a/trychange.py b/trychange.py index ea21e2928..0105fd1a5 100755 --- a/trychange.py +++ b/trychange.py @@ -173,6 +173,8 @@ class SVN(SCM): self.options.diff = self.GenerateDiff(adjusted_paths, root=source_root) self.change_info = gcl.LoadChangelistInfoForMultiple(self.options.name, gcl.GetRepositoryRoot(), True, True) + if not self.options.email: + self.options.email = scm.SVN.GetEmail(gcl.GetRepositoryRoot()) class GIT(SCM): @@ -189,10 +191,6 @@ class GIT(SCM): diff[i] = '--- %s' % diff[i+1][4:] return ''.join(diff) - def GetEmail(self): - # TODO: check for errors here? - return upload.RunShell(['git', 'config', 'user.email']).strip() - def GetFileNames(self): """Return the list of files in the diff.""" return self.options.files @@ -220,7 +218,7 @@ class GIT(SCM): if not self.options.name: self.options.name = self.GetPatchName() if not self.options.email: - self.options.email = self.GetEmail() + self.options.email = scm.GIT.GetEmail('.') def _ParseSendChangeOptions(options):