Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(36)

Side by Side Diff: scm.py

Issue 507061: Move GenerateDiff into a common function. (Closed)
Patch Set: mult Created 11 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « gcl.py ('k') | tests/gclient_scm_test.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 # Copyright (c) 2006-2009 The Chromium Authors. All rights reserved. 1 # Copyright (c) 2006-2009 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be 2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file. 3 # found in the LICENSE file.
4 4
5 """SCM-specific utility classes.""" 5 """SCM-specific utility classes."""
6 6
7 import os 7 import os
8 import re 8 import re
9 import shutil
9 import subprocess 10 import subprocess
10 import sys 11 import sys
11 import tempfile 12 import tempfile
12 import xml.dom.minidom 13 import xml.dom.minidom
13 14
14 import gclient_utils 15 import gclient_utils
15 16
16 17
17 class GIT(object): 18 class GIT(object):
18 COMMAND = "git" 19 COMMAND = "git"
19 20
20 @staticmethod 21 @staticmethod
21 def Capture(args, in_directory=None, print_error=True): 22 def Capture(args, in_directory=None, print_error=True, error_ok=False):
22 """Runs git, capturing output sent to stdout as a string. 23 """Runs git, capturing output sent to stdout as a string.
23 24
24 Args: 25 Args:
25 args: A sequence of command line parameters to be passed to git. 26 args: A sequence of command line parameters to be passed to git.
26 in_directory: The directory where git is to be run. 27 in_directory: The directory where git is to be run.
27 28
28 Returns: 29 Returns:
29 The output sent to stdout as a string. 30 The output sent to stdout as a string.
30 """ 31 """
31 c = [GIT.COMMAND] 32 c = [GIT.COMMAND]
32 c.extend(args) 33 c.extend(args)
33 34 try:
34 # *Sigh*: Windows needs shell=True, or else it won't search %PATH% for 35 return gclient_utils.CheckCall(c, in_directory, print_error)
35 # the git.exe executable, but shell=True makes subprocess on Linux fail 36 except gclient_utils.CheckCallError:
36 # when it's called with a list because it only tries to execute the 37 if error_ok:
37 # first string ("git"). 38 return ''
38 stderr = None 39 raise
39 if not print_error:
40 stderr = subprocess.PIPE
41 return subprocess.Popen(c,
42 cwd=in_directory,
43 shell=sys.platform.startswith('win'),
44 stdout=subprocess.PIPE,
45 stderr=stderr).communicate()[0]
46
47 40
48 @staticmethod 41 @staticmethod
49 def CaptureStatus(files, upstream_branch='origin'): 42 def CaptureStatus(files, upstream_branch='origin'):
50 """Returns git status. 43 """Returns git status.
51 44
52 @files can be a string (one file) or a list of files. 45 @files can be a string (one file) or a list of files.
53 46
54 Returns an array of (status, file) tuples.""" 47 Returns an array of (status, file) tuples."""
55 command = ["diff", "--name-status", "-r", "%s.." % upstream_branch] 48 command = ["diff", "--name-status", "-r", "%s.." % upstream_branch]
56 if not files: 49 if not files:
(...skipping 11 matching lines...) Expand all
68 if not m: 61 if not m:
69 raise Exception("status currently unsupported: %s" % statusline) 62 raise Exception("status currently unsupported: %s" % statusline)
70 results.append(('%s ' % m.group(1), m.group(2))) 63 results.append(('%s ' % m.group(1), m.group(2)))
71 return results 64 return results
72 65
73 @staticmethod 66 @staticmethod
74 def GetEmail(repo_root): 67 def GetEmail(repo_root):
75 """Retrieves the user email address if known.""" 68 """Retrieves the user email address if known."""
76 # We could want to look at the svn cred when it has a svn remote but it 69 # We could want to look at the svn cred when it has a svn remote but it
77 # should be fine for now, users should simply configure their git settings. 70 # should be fine for now, users should simply configure their git settings.
78 return GIT.Capture(['config', 'user.email'], repo_root).strip() 71 return GIT.Capture(['config', 'user.email'],
72 repo_root, error_ok=True).strip()
73
74 @staticmethod
75 def ShortBranchName(branch):
76 """Converts a name like 'refs/heads/foo' to just 'foo'."""
77 return branch.replace('refs/heads/', '')
78
79 @staticmethod
80 def GetBranchRef(cwd):
81 """Returns the short branch name, e.g. 'master'."""
82 return GIT.Capture(['symbolic-ref', 'HEAD'], cwd).strip()
83
84 @staticmethod
85 def IsGitSvn(cwd):
86 """Returns true if this repo looks like it's using git-svn."""
87 # If you have any "svn-remote.*" config keys, we think you're using svn.
88 try:
89 GIT.Capture(['config', '--get-regexp', r'^svn-remote\.'], cwd)
90 return True
91 except gclient_utils.CheckCallError:
92 return False
93
94 @staticmethod
95 def GetSVNBranch(cwd):
96 """Returns the svn branch name if found."""
97 # Try to figure out which remote branch we're based on.
98 # Strategy:
99 # 1) find all git-svn branches and note their svn URLs.
100 # 2) iterate through our branch history and match up the URLs.
101
102 # regexp matching the git-svn line that contains the URL.
103 git_svn_re = re.compile(r'^\s*git-svn-id: (\S+)@', re.MULTILINE)
104
105 # Get the refname and svn url for all refs/remotes/*.
106 remotes = GIT.Capture(
107 ['for-each-ref', '--format=%(refname)', 'refs/remotes'],
108 cwd).splitlines()
109 svn_refs = {}
110 for ref in remotes:
111 match = git_svn_re.search(
112 GIT.Capture(['cat-file', '-p', ref], cwd))
113 if match:
114 svn_refs[match.group(1)] = ref
115
116 svn_branch = ''
117 if len(svn_refs) == 1:
118 # Only one svn branch exists -- seems like a good candidate.
119 svn_branch = svn_refs.values()[0]
120 elif len(svn_refs) > 1:
121 # We have more than one remote branch available. We don't
122 # want to go through all of history, so read a line from the
123 # pipe at a time.
124 # The -100 is an arbitrary limit so we don't search forever.
125 cmd = ['git', 'log', '-100', '--pretty=medium']
126 proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, cwd=cwd)
127 for line in proc.stdout:
128 match = git_svn_re.match(line)
129 if match:
130 url = match.group(1)
131 if url in svn_refs:
132 svn_branch = svn_refs[url]
133 proc.stdout.close() # Cut pipe.
134 break
135 return svn_branch
136
137 @staticmethod
138 def FetchUpstreamTuple(cwd):
139 """Returns a tuple containg remote and remote ref,
140 e.g. 'origin', 'refs/heads/master'
141 """
142 remote = '.'
143 branch = GIT.ShortBranchName(GIT.GetBranchRef(cwd))
144 upstream_branch = None
145 upstream_branch = GIT.Capture(
146 ['config', 'branch.%s.merge' % branch], error_ok=True).strip()
147 if upstream_branch:
148 remote = GIT.Capture(
149 ['config', 'branch.%s.remote' % branch],
150 error_ok=True).strip()
151 else:
152 # Fall back on trying a git-svn upstream branch.
153 if GIT.IsGitSvn(cwd):
154 upstream_branch = GIT.GetSVNBranch(cwd)
155 # Fall back on origin/master if it exits.
156 if not upstream_branch:
157 GIT.Capture(['branch', '-r']).split().count('origin/master')
158 remote = 'origin'
159 upstream_branch = 'refs/heads/master'
160 return remote, upstream_branch
161
162 @staticmethod
163 def GetUpstream(cwd):
164 """Gets the current branch's upstream branch."""
165 remote, upstream_branch = GIT.FetchUpstreamTuple(cwd)
166 if remote is not '.':
167 upstream_branch = upstream_branch.replace('heads', 'remotes/' + remote)
168 return upstream_branch
169
170 @staticmethod
171 def GenerateDiff(cwd, branch=None):
172 """Diffs against the upstream branch or optionally another branch."""
173 if not branch:
174 branch = GIT.GetUpstream(cwd)
175 diff = GIT.Capture(['diff-tree', '-p', '--no-prefix', branch, 'HEAD'],
176 cwd).splitlines(True)
177 for i in range(len(diff)):
178 # In the case of added files, replace /dev/null with the path to the
179 # file being added.
180 if diff[i].startswith('--- /dev/null'):
181 diff[i] = '--- %s' % diff[i+1][4:]
182 return ''.join(diff)
79 183
80 184
81 class SVN(object): 185 class SVN(object):
82 COMMAND = "svn" 186 COMMAND = "svn"
83 187
84 @staticmethod 188 @staticmethod
85 def Run(args, in_directory): 189 def Run(args, in_directory):
86 """Runs svn, sending output to stdout. 190 """Runs svn, sending output to stdout.
87 191
88 Args: 192 Args:
(...skipping 296 matching lines...) Expand 10 before | Expand all | Expand 10 after
385 empty string is also returned. 489 empty string is also returned.
386 """ 490 """
387 output = SVN.Capture(["propget", property_name, file]) 491 output = SVN.Capture(["propget", property_name, file])
388 if (output.startswith("svn: ") and 492 if (output.startswith("svn: ") and
389 output.endswith("is not under version control")): 493 output.endswith("is not under version control")):
390 return "" 494 return ""
391 else: 495 else:
392 return output 496 return output
393 497
394 @staticmethod 498 @staticmethod
395 def DiffItem(filename): 499 def DiffItem(filename, full_move=False):
396 """Diff a single file""" 500 """Diffs a single file.
501
502 Be sure to be in the appropriate directory before calling to have the
503 expected relative path."""
397 # Use svn info output instead of os.path.isdir because the latter fails 504 # Use svn info output instead of os.path.isdir because the latter fails
398 # when the file is deleted. 505 # when the file is deleted.
399 if SVN.CaptureInfo(filename).get("Node Kind") == "directory": 506 if SVN.CaptureInfo(filename).get("Node Kind") == "directory":
400 return None 507 return None
401 # If the user specified a custom diff command in their svn config file, 508 # If the user specified a custom diff command in their svn config file,
402 # then it'll be used when we do svn diff, which we don't want to happen 509 # then it'll be used when we do svn diff, which we don't want to happen
403 # since we want the unified diff. Using --diff-cmd=diff doesn't always 510 # since we want the unified diff. Using --diff-cmd=diff doesn't always
404 # work, since they can have another diff executable in their path that 511 # work, since they can have another diff executable in their path that
405 # gives different line endings. So we use a bogus temp directory as the 512 # gives different line endings. So we use a bogus temp directory as the
406 # config directory, which gets around these problems. 513 # config directory, which gets around these problems.
407 if sys.platform.startswith("win"): 514 bogus_dir = tempfile.mkdtemp()
408 parent_dir = tempfile.gettempdir() 515 try:
409 else: 516 # Grabs the diff data.
410 parent_dir = sys.path[0] # tempdir is not secure. 517 data = SVN.Capture(["diff", "--config-dir", bogus_dir, filename], None)
411 bogus_dir = os.path.join(parent_dir, "temp_svn_config") 518 if data:
412 if not os.path.exists(bogus_dir): 519 pass
413 os.mkdir(bogus_dir) 520 elif SVN.IsMoved(filename):
414 # Grabs the diff data. 521 if full_move:
415 data = SVN.Capture(["diff", "--config-dir", bogus_dir, filename], None) 522 file_content = gclient_utils.FileRead(filename, 'rb')
523 # Prepend '+' to every lines.
524 file_content = ['+' + i for i in file_content.splitlines(True)]
525 nb_lines = len(file_content)
526 # We need to use / since patch on unix will fail otherwise.
527 filename = filename.replace('\\', '/')
528 data = "Index: %s\n" % filename
529 data += '=' * 67 + '\n'
530 # Note: Should we use /dev/null instead?
531 data += "--- %s\n" % filename
532 data += "+++ %s\n" % filename
533 data += "@@ -0,0 +1,%d @@\n" % nb_lines
534 data += ''.join(file_content)
535 else:
536 # svn diff on a mv/cp'd file outputs nothing.
537 # We put in an empty Index entry so upload.py knows about them.
538 data = "Index: %s\n" % filename
539 else:
540 # The file is not modified anymore. It should be removed from the set.
541 pass
542 finally:
543 shutil.rmtree(bogus_dir)
544 return data
416 545
417 # We know the diff will be incorrectly formatted. Fix it. 546 @staticmethod
418 if SVN.IsMoved(filename): 547 def GenerateDiff(filenames, root=None, full_move=False):
419 file_content = gclient_utils.FileRead(filename, 'rb') 548 """Returns a string containing the diff for the given file list.
420 # Prepend '+' to every lines. 549
421 file_content = ['+' + i for i in file_content.splitlines(True)] 550 The files in the list should either be absolute paths or relative to the
422 nb_lines = len(file_content) 551 given root. If no root directory is provided, the repository root will be
423 # We need to use / since patch on unix will fail otherwise. 552 used.
424 filename = filename.replace('\\', '/') 553 The diff will always use relative paths.
425 data = "Index: %s\n" % filename 554 """
426 data += ("=============================================================" 555 previous_cwd = os.getcwd()
427 "======\n") 556 root = os.path.join(root or SVN.GetCheckoutRoot(previous_cwd), '')
428 # Note: Should we use /dev/null instead? 557 def RelativePath(path, root):
429 data += "--- %s\n" % filename 558 """We must use relative paths."""
430 data += "+++ %s\n" % filename 559 if path.startswith(root):
431 data += "@@ -0,0 +1,%d @@\n" % nb_lines 560 return path[len(root):]
432 data += ''.join(file_content) 561 return path
433 return data 562 try:
563 os.chdir(root)
564 diff = "".join(filter(None,
565 [SVN.DiffItem(RelativePath(f, root),
566 full_move=full_move)
567 for f in filenames]))
568 finally:
569 os.chdir(previous_cwd)
570 return diff
571
434 572
435 @staticmethod 573 @staticmethod
436 def GetEmail(repo_root): 574 def GetEmail(repo_root):
437 """Retrieves the svn account which we assume is an email address.""" 575 """Retrieves the svn account which we assume is an email address."""
438 infos = SVN.CaptureInfo(repo_root) 576 infos = SVN.CaptureInfo(repo_root)
439 uuid = infos.get('UUID') 577 uuid = infos.get('UUID')
440 root = infos.get('Repository Root') 578 root = infos.get('Repository Root')
441 if not root: 579 if not root:
442 return None 580 return None
443 581
(...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after
498 if not cur_dir_repo_root: 636 if not cur_dir_repo_root:
499 return None 637 return None
500 638
501 while True: 639 while True:
502 parent = os.path.dirname(directory) 640 parent = os.path.dirname(directory)
503 if (SVN.CaptureInfo(parent, print_error=False).get( 641 if (SVN.CaptureInfo(parent, print_error=False).get(
504 "Repository Root") != cur_dir_repo_root): 642 "Repository Root") != cur_dir_repo_root):
505 break 643 break
506 directory = parent 644 directory = parent
507 return directory 645 return directory
OLDNEW
« no previous file with comments | « gcl.py ('k') | tests/gclient_scm_test.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698