@ -6,6 +6,7 @@
import os
import os
import re
import re
import shutil
import subprocess
import subprocess
import sys
import sys
import tempfile
import tempfile
@ -18,7 +19,7 @@ class GIT(object):
COMMAND = " git "
COMMAND = " git "
@staticmethod
@staticmethod
def Capture ( args , in_directory = None , print_error = True ):
def Capture ( args , in_directory = None , print_error = True , error_ok = False ):
""" Runs git, capturing output sent to stdout as a string.
""" Runs git, capturing output sent to stdout as a string.
Args :
Args :
@ -30,20 +31,12 @@ class GIT(object):
"""
"""
c = [ GIT . COMMAND ]
c = [ GIT . COMMAND ]
c . extend ( args )
c . extend ( args )
try :
# *Sigh*: Windows needs shell=True, or else it won't search %PATH% for
return gclient_utils . CheckCall ( c , in_directory , print_error )
# the git.exe executable, but shell=True makes subprocess on Linux fail
except gclient_utils . CheckCallError :
# when it's called with a list because it only tries to execute the
if error_ok :
# first string ("git").
return ' '
stderr = None
raise
if not print_error :
stderr = subprocess . PIPE
return subprocess . Popen ( c ,
cwd = in_directory ,
shell = sys . platform . startswith ( ' win ' ) ,
stdout = subprocess . PIPE ,
stderr = stderr ) . communicate ( ) [ 0 ]
@staticmethod
@staticmethod
def CaptureStatus ( files , upstream_branch = ' origin ' ) :
def CaptureStatus ( files , upstream_branch = ' origin ' ) :
@ -75,7 +68,118 @@ class GIT(object):
""" Retrieves the user email address if known. """
""" Retrieves the user email address if known. """
# We could want to look at the svn cred when it has a svn remote but it
# 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.
# should be fine for now, users should simply configure their git settings.
return GIT . Capture ( [ ' config ' , ' user.email ' ] , repo_root ) . strip ( )
return GIT . Capture ( [ ' config ' , ' user.email ' ] ,
repo_root , error_ok = True ) . strip ( )
@staticmethod
def ShortBranchName ( branch ) :
""" Converts a name like ' refs/heads/foo ' to just ' foo ' . """
return branch . replace ( ' refs/heads/ ' , ' ' )
@staticmethod
def GetBranchRef ( cwd ) :
""" Returns the short branch name, e.g. ' master ' . """
return GIT . Capture ( [ ' symbolic-ref ' , ' HEAD ' ] , cwd ) . strip ( )
@staticmethod
def IsGitSvn ( cwd ) :
""" Returns true if this repo looks like it ' s using git-svn. """
# If you have any "svn-remote.*" config keys, we think you're using svn.
try :
GIT . Capture ( [ ' config ' , ' --get-regexp ' , r ' ^svn-remote \ . ' ] , cwd )
return True
except gclient_utils . CheckCallError :
return False
@staticmethod
def GetSVNBranch ( cwd ) :
""" Returns the svn branch name if found. """
# Try to figure out which remote branch we're based on.
# Strategy:
# 1) find all git-svn branches and note their svn URLs.
# 2) iterate through our branch history and match up the URLs.
# regexp matching the git-svn line that contains the URL.
git_svn_re = re . compile ( r ' ^ \ s*git-svn-id: ( \ S+)@ ' , re . MULTILINE )
# Get the refname and svn url for all refs/remotes/*.
remotes = GIT . Capture (
[ ' for-each-ref ' , ' --format= % (refname) ' , ' refs/remotes ' ] ,
cwd ) . splitlines ( )
svn_refs = { }
for ref in remotes :
match = git_svn_re . search (
GIT . Capture ( [ ' cat-file ' , ' -p ' , ref ] , cwd ) )
if match :
svn_refs [ match . group ( 1 ) ] = ref
svn_branch = ' '
if len ( svn_refs ) == 1 :
# Only one svn branch exists -- seems like a good candidate.
svn_branch = svn_refs . values ( ) [ 0 ]
elif len ( svn_refs ) > 1 :
# We have more than one remote branch available. We don't
# want to go through all of history, so read a line from the
# pipe at a time.
# The -100 is an arbitrary limit so we don't search forever.
cmd = [ ' git ' , ' log ' , ' -100 ' , ' --pretty=medium ' ]
proc = subprocess . Popen ( cmd , stdout = subprocess . PIPE , cwd = cwd )
for line in proc . stdout :
match = git_svn_re . match ( line )
if match :
url = match . group ( 1 )
if url in svn_refs :
svn_branch = svn_refs [ url ]
proc . stdout . close ( ) # Cut pipe.
break
return svn_branch
@staticmethod
def FetchUpstreamTuple ( cwd ) :
""" Returns a tuple containg remote and remote ref,
e . g . ' origin ' , ' refs/heads/master '
"""
remote = ' . '
branch = GIT . ShortBranchName ( GIT . GetBranchRef ( cwd ) )
upstream_branch = None
upstream_branch = GIT . Capture (
[ ' config ' , ' branch. %s .merge ' % branch ] , error_ok = True ) . strip ( )
if upstream_branch :
remote = GIT . Capture (
[ ' config ' , ' branch. %s .remote ' % branch ] ,
error_ok = True ) . strip ( )
else :
# Fall back on trying a git-svn upstream branch.
if GIT . IsGitSvn ( cwd ) :
upstream_branch = GIT . GetSVNBranch ( cwd )
# Fall back on origin/master if it exits.
if not upstream_branch :
GIT . Capture ( [ ' branch ' , ' -r ' ] ) . split ( ) . count ( ' origin/master ' )
remote = ' origin '
upstream_branch = ' refs/heads/master '
return remote , upstream_branch
@staticmethod
def GetUpstream ( cwd ) :
""" Gets the current branch ' s upstream branch. """
remote , upstream_branch = GIT . FetchUpstreamTuple ( cwd )
if remote is not ' . ' :
upstream_branch = upstream_branch . replace ( ' heads ' , ' remotes/ ' + remote )
return upstream_branch
@staticmethod
def GenerateDiff ( cwd , branch = None ) :
""" Diffs against the upstream branch or optionally another branch. """
if not branch :
branch = GIT . GetUpstream ( cwd )
diff = GIT . Capture ( [ ' diff-tree ' , ' -p ' , ' --no-prefix ' , branch , ' HEAD ' ] ,
cwd ) . splitlines ( True )
for i in range ( len ( diff ) ) :
# In the case of added files, replace /dev/null with the path to the
# file being added.
if diff [ i ] . startswith ( ' --- /dev/null ' ) :
diff [ i ] = ' --- %s ' % diff [ i + 1 ] [ 4 : ]
return ' ' . join ( diff )
class SVN ( object ) :
class SVN ( object ) :
@ -392,8 +496,11 @@ class SVN(object):
return output
return output
@staticmethod
@staticmethod
def DiffItem ( filename ) :
def DiffItem ( filename , full_move = False ) :
""" Diff a single file """
""" Diffs a single file.
Be sure to be in the appropriate directory before calling to have the
expected relative path . """
# Use svn info output instead of os.path.isdir because the latter fails
# Use svn info output instead of os.path.isdir because the latter fails
# when the file is deleted.
# when the file is deleted.
if SVN . CaptureInfo ( filename ) . get ( " Node Kind " ) == " directory " :
if SVN . CaptureInfo ( filename ) . get ( " Node Kind " ) == " directory " :
@ -404,34 +511,65 @@ class SVN(object):
# work, since they can have another diff executable in their path that
# work, since they can have another diff executable in their path that
# gives different line endings. So we use a bogus temp directory as the
# gives different line endings. So we use a bogus temp directory as the
# config directory, which gets around these problems.
# config directory, which gets around these problems.
if sys . platform . startswith ( " win " ) :
bogus_dir = tempfile . mkdtemp ( )
parent_dir = tempfile . gettempdir ( )
try :
else :
# Grabs the diff data.
parent_dir = sys . path [ 0 ] # tempdir is not secure.
data = SVN . Capture ( [ " diff " , " --config-dir " , bogus_dir , filename ] , None )
bogus_dir = os . path . join ( parent_dir , " temp_svn_config " )
if data :
if not os . path . exists ( bogus_dir ) :
pass
os . mkdir ( bogus_dir )
elif SVN . IsMoved ( filename ) :
# Grabs the diff data.
if full_move :
data = SVN . Capture ( [ " diff " , " --config-dir " , bogus_dir , filename ] , None )
file_content = gclient_utils . FileRead ( filename , ' rb ' )
# Prepend '+' to every lines.
# We know the diff will be incorrectly formatted. Fix it.
file_content = [ ' + ' + i for i in file_content . splitlines ( True ) ]
if SVN . IsMoved ( filename ) :
nb_lines = len ( file_content )
file_content = gclient_utils . FileRead ( filename , ' rb ' )
# We need to use / since patch on unix will fail otherwise.
# Prepend '+' to every lines.
filename = filename . replace ( ' \\ ' , ' / ' )
file_content = [ ' + ' + i for i in file_content . splitlines ( True ) ]
data = " Index: %s \n " % filename
nb_lines = len ( file_content )
data + = ' = ' * 67 + ' \n '
# We need to use / since patch on unix will fail otherwise.
# Note: Should we use /dev/null instead?
filename = filename . replace ( ' \\ ' , ' / ' )
data + = " --- %s \n " % filename
data = " Index: %s \n " % filename
data + = " +++ %s \n " % filename
data + = ( " ============================================================= "
data + = " @@ -0,0 +1, %d @@ \n " % nb_lines
" ====== \n " )
data + = ' ' . join ( file_content )
# Note: Should we use /dev/null instead?
else :
data + = " --- %s \n " % filename
# svn diff on a mv/cp'd file outputs nothing.
data + = " +++ %s \n " % filename
# We put in an empty Index entry so upload.py knows about them.
data + = " @@ -0,0 +1, %d @@ \n " % nb_lines
data = " Index: %s \n " % filename
data + = ' ' . join ( file_content )
else :
# The file is not modified anymore. It should be removed from the set.
pass
finally :
shutil . rmtree ( bogus_dir )
return data
return data
@staticmethod
def GenerateDiff ( filenames , root = None , full_move = False ) :
""" Returns a string containing the diff for the given file list.
The files in the list should either be absolute paths or relative to the
given root . If no root directory is provided , the repository root will be
used .
The diff will always use relative paths .
"""
previous_cwd = os . getcwd ( )
root = os . path . join ( root or SVN . GetCheckoutRoot ( previous_cwd ) , ' ' )
def RelativePath ( path , root ) :
""" We must use relative paths. """
if path . startswith ( root ) :
return path [ len ( root ) : ]
return path
try :
os . chdir ( root )
diff = " " . join ( filter ( None ,
[ SVN . DiffItem ( RelativePath ( f , root ) ,
full_move = full_move )
for f in filenames ] ) )
finally :
os . chdir ( previous_cwd )
return diff
@staticmethod
@staticmethod
def GetEmail ( repo_root ) :
def GetEmail ( repo_root ) :
""" Retrieves the svn account which we assume is an email address. """
""" Retrieves the svn account which we assume is an email address. """