def Change(change_info, args): """Creates/edits a changelist.""" silent = FilterFlag(args, "--silent") # Verify the user is running the change command from a read-write checkout. svn_info = SVN.CaptureInfo('.') if not svn_info: ErrorExit( "Current checkout is unversioned. Please retry with a versioned " "directory.") if (len(args) == 1): filename = args[0] f = open(filename, 'rU') override_description = f.read() f.close() else: override_description = None if change_info.issue and not change_info.NeedsUpload(): try: description = GetIssueDescription(change_info.issue) except urllib2.HTTPError, err: if err.code == 404: # The user deleted the issue in Rietveld, so forget the old issue id. description = change_info.description change_info.issue = 0 change_info.Save() else: ErrorExit("Error getting the description from Rietveld: " + err)
def Load(changename, local_root, fail_on_not_found, update_status): """Gets information about a changelist. Args: fail_on_not_found: if True, this function will quit the program if the changelist doesn't exist. update_status: if True, the svn status will be updated for all the files and unchanged files will be removed. Returns: a ChangeInfo object. """ info_file = GetChangelistInfoFile(changename) if not os.path.exists(info_file): if fail_on_not_found: ErrorExit("Changelist " + changename + " not found.") return ChangeInfo(changename, 0, 0, '', None, local_root, needs_upload=False) split_data = ReadFile(info_file).split(ChangeInfo._SEPARATOR, 2) if len(split_data) != 3: ErrorExit("Changelist file %s is corrupt" % info_file) items = split_data[0].split(', ') issue = 0 patchset = 0 needs_upload = False if items[0]: issue = int(items[0]) if len(items) > 1: patchset = int(items[1]) if len(items) > 2: needs_upload = (items[2] == "dirty") files = [] for line in split_data[1].splitlines(): status = line[:7] filename = line[7:] files.append((status, filename)) description = split_data[2] save = False if update_status: for item in files: filename = os.path.join(local_root, item[1]) status_result = SVN.CaptureStatus(filename) if not status_result or not status_result[0][0]: # File has been reverted. save = True files.remove(item) continue status = status_result[0][0] if status != item[0]: save = True files[files.index(item)] = (status, item[1]) change_info = ChangeInfo(changename, issue, patchset, description, files, local_root, needs_upload) if save: change_info.Save() return change_info
def GenerateDiff(files, root=None): """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. """ previous_cwd = os.getcwd() if root is None: os.chdir(GetRepositoryRoot()) else: os.chdir(root) diff = [] for filename in files: # TODO(maruel): Use SVN.DiffItem(). # Use svn info output instead of os.path.isdir because the latter fails # when the file is deleted. if SVN.CaptureInfo(filename).get('Node Kind') == 'directory': continue # If the user specified a custom diff command in their svn config file, # then it'll be used when we do svn diff, which we don't want to happen # since we want the unified diff. Using --diff-cmd=diff doesn't always # 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 # config directory, which gets around these problems. if sys.platform.startswith("win"): parent_dir = tempfile.gettempdir() else: parent_dir = sys.path[0] # tempdir is not secure. bogus_dir = os.path.join(parent_dir, "temp_svn_config") if not os.path.exists(bogus_dir): os.mkdir(bogus_dir) output = RunShell(["svn", "diff", "--config-dir", bogus_dir, filename]) if output: diff.append(output) elif SVN.IsMoved(filename): # svn diff on a mv/cp'd file outputs nothing. # We put in an empty Index entry so upload.py knows about them. diff.append("\nIndex: %s\n" % filename) else: # The file is not modified anymore. It should be removed from the set. pass os.chdir(previous_cwd) return "".join(diff)
def UnknownFiles(extra_args): """Runs svn status and returns unknown files. Any args in |extra_args| are passed to the tool to support giving alternate code locations. """ return [ item[1] for item in SVN.CaptureStatus(extra_args) if item[0][0] == '?' ]
def Load(changename, local_root, fail_on_not_found, update_status): """Gets information about a changelist. Args: fail_on_not_found: if True, this function will quit the program if the changelist doesn't exist. update_status: if True, the svn status will be updated for all the files and unchanged files will be removed. Returns: a ChangeInfo object. """ info_file = GetChangelistInfoFile(changename) if not os.path.exists(info_file): if fail_on_not_found: ErrorExit("Changelist " + changename + " not found.") return ChangeInfo(changename, 0, 0, '', None, local_root, None, False) content = gclient_utils.FileRead(info_file) save = False try: values = ChangeInfo._LoadNewFormat(content) except ValueError: try: values = ChangeInfo._LoadOldFormat(content) save = True except ValueError: ErrorExit( ('Changelist file %s is corrupt.\n' 'Either run "gcl delete %s" or manually edit the file') % ( info_file, changename)) files = values['files'] if update_status: for item in files[:]: status_result = SVN.CaptureStatus(item[1], local_root) if not status_result or not status_result[0][0]: # File has been reverted. save = True files.remove(item) continue status = status_result[0][0] if status != item[0]: save = True files[files.index(item)] = (status, item[1]) change_info = ChangeInfo( changename, values['issue'], values['patchset'], values['description'], files, local_root, values.get('rietveld'), values['needs_upload']) if save: change_info.Save() return change_info
def GetRepositoryRoot(): """Returns the top level directory of the current repository. The directory is returned as an absolute path. """ global REPOSITORY_ROOT if not REPOSITORY_ROOT: REPOSITORY_ROOT = SVN.GetCheckoutRoot(os.getcwd()) if not REPOSITORY_ROOT: raise gclient_utils.Error("gcl run outside of repository") return REPOSITORY_ROOT
def GetRepositoryRoot(): """Returns the top level directory of the current repository. The directory is returned as an absolute path. """ global REPOSITORY_ROOT if not REPOSITORY_ROOT: infos = SVN.CaptureInfo(os.getcwd(), print_error=False) cur_dir_repo_root = infos.get("Repository Root") if not cur_dir_repo_root: raise gclient_utils.Error("gcl run outside of repository") REPOSITORY_ROOT = os.getcwd() while True: parent = os.path.dirname(REPOSITORY_ROOT) if (SVN.CaptureInfo(parent, print_error=False).get("Repository Root") != cur_dir_repo_root): break REPOSITORY_ROOT = parent return REPOSITORY_ROOT
def CMDchange(args): """Creates or edits a changelist. Only scans the current directory and subdirectories. """ # Verify the user is running the change command from a read-write checkout. svn_info = SVN.CaptureLocalInfo([], '.') if not svn_info: ErrorExit("Current checkout is unversioned. Please retry with a versioned " "directory.") if len(args) == 0: # Generate a random changelist name. changename = GenerateChangeName() elif args[0] == '--force': changename = GenerateChangeName() else: changename = args[0] change_info = ChangeInfo.Load(changename, GetRepositoryRoot(), False, True) if len(args) == 2: if not os.path.isfile(args[1]): ErrorExit('The change "%s" doesn\'t exist.' % args[1]) f = open(args[1], 'rU') override_description = f.read() f.close() else: override_description = None if change_info.issue and not change_info.NeedsUpload(): try: description = change_info.GetIssueDescription() except urllib2.HTTPError, err: if err.code == 404: # The user deleted the issue in Rietveld, so forget the old issue id. description = change_info.description change_info.issue = 0 change_info.Save() else: ErrorExit("Error getting the description from Rietveld: " + err)
def GetModifiedFiles(): """Returns a set that maps from changelist name to (status,filename) tuples. Files not in a changelist have an empty changelist name. Filenames are in relation to the top level directory of the current repository. Note that only the current directory and subdirectories are scanned, in order to improve performance while still being flexible. """ files = {} # Since the files are normalized to the root folder of the repositary, figure # out what we need to add to the paths. dir_prefix = os.getcwd()[len(GetRepositoryRoot()):].strip(os.sep) # Get a list of all files in changelists. files_in_cl = {} for cl in GetCLs(): change_info = ChangeInfo.Load(cl, GetRepositoryRoot(), fail_on_not_found=True, update_status=False) for status, filename in change_info.GetFiles(): files_in_cl[filename] = change_info.name # Get all the modified files. status_result = SVN.CaptureStatus(None) for line in status_result: status = line[0] filename = line[1] if status[0] == "?": continue if dir_prefix: filename = os.path.join(dir_prefix, filename) change_list_name = "" if filename in files_in_cl: change_list_name = files_in_cl[filename] files.setdefault(change_list_name, []).append((status, filename)) return files
def GetCachedFile(filename, max_age=60 * 60 * 24 * 3, use_root=False): """Retrieves a file from the repository and caches it in GetCacheDir() for max_age seconds. use_root: If False, look up the arborescence for the first match, otherwise go directory to the root repository. Note: The cache will be inconsistent if the same file is retrieved with both use_root=True and use_root=False. Don't be stupid. """ global FILES_CACHE if filename not in FILES_CACHE: # Don't try to look up twice. FILES_CACHE[filename] = None # First we check if we have a cached version. try: cached_file = os.path.join(GetCacheDir(), filename) except gclient_utils.Error: return None if (not os.path.exists(cached_file) or os.stat(cached_file).st_mtime > max_age): local_dir = os.path.dirname(os.path.abspath(filename)) local_base = os.path.basename(filename) dir_info = SVN.CaptureInfo(".") repo_root = dir_info["Repository Root"] if use_root: url_path = repo_root else: url_path = dir_info["URL"] content = "" while True: # First, look for a locally modified version of the file if we can. r = "" if not use_root: local_path = os.path.join(local_dir, local_base) r = SVN.CaptureStatus((local_path, )) rc = -1 if r: status = r[0][0] rc = 0 if not rc and status[0] in ('A', 'M'): content = ReadFile(local_path) rc = 0 else: # Look in the repository if we didn't find something local. svn_path = url_path + "/" + filename content, rc = RunShellWithReturnCode( ["svn", "cat", svn_path]) if not rc: # Exit the loop if the file was found. Override content. break # Make sure to mark settings as empty if not found. content = "" if url_path == repo_root: # Reached the root. Abandoning search. break # Go up one level to try again. url_path = os.path.dirname(url_path) local_dir = os.path.dirname(local_dir) # Write a cached version even if there isn't a file, so we don't try to # fetch it each time. WriteFile(cached_file, content) else: content = ReadFile(cached_file) # Keep the content cached in memory. FILES_CACHE[filename] = content return FILES_CACHE[filename]
def UnknownFiles(): """Runs svn status and returns unknown files.""" return [item[1] for item in SVN.CaptureStatus([]) if item[0][0] == '?']
def GenerateDiff(files, root=None): return SVN.GenerateDiff(files, root=root)
def GetCachedFile(filename, max_age=60*60*24*3, use_root=False): """Retrieves a file from the repository and caches it in GetCacheDir() for max_age seconds. use_root: If False, look up the arborescence for the first match, otherwise go directory to the root repository. Note: The cache will be inconsistent if the same file is retrieved with both use_root=True and use_root=False. Don't be stupid. """ if filename not in FILES_CACHE: # Don't try to look up twice. FILES_CACHE[filename] = None # First we check if we have a cached version. try: cached_file = os.path.join(GetCacheDir(), filename) except gclient_utils.Error: return None if (not os.path.exists(cached_file) or (time.time() - os.stat(cached_file).st_mtime) > max_age): dir_info = SVN.CaptureInfo('.') repo_root = dir_info['Repository Root'] if use_root: url_path = repo_root else: url_path = dir_info['URL'] while True: # Look in the repository at the current level for the file. for _ in range(5): content = None try: # Take advantage of the fact that svn won't output to stderr in case # of success but will do in case of failure so don't mind putting # stderr into content_array. content_array = [] svn_path = url_path + '/' + filename args = ['svn', 'cat', svn_path] if sys.platform != 'darwin': # MacOSX 10.5.2 has a bug with svn 1.4.4 that will trigger the # 'Can\'t get username or password' and can be fixed easily. # The fix doesn't work if the user upgraded to svn 1.6.x. Bleh. # I don't have time to fix their broken stuff. args.append('--non-interactive') gclient_utils.CheckCallAndFilter( args, cwd='.', filter_fn=content_array.append) # Exit the loop if the file was found. Override content. content = '\n'.join(content_array) break except gclient_utils.Error: if content_array[0].startswith( 'svn: Can\'t get username or password'): ErrorExit('Your svn credentials expired. Please run svn update ' 'to fix the cached credentials') if content_array[0].startswith('svn: Can\'t get password'): ErrorExit('If are using a Mac and svn --version shows 1.4.x, ' 'please hack gcl.py to remove --non-interactive usage, it\'s' 'a bug on your installed copy') if not content_array[0].startswith('svn: File not found:'): # Try again. continue if content: break if url_path == repo_root: # Reached the root. Abandoning search. break # Go up one level to try again. url_path = os.path.dirname(url_path) if content is not None or filename != CODEREVIEW_SETTINGS_FILE: # Write a cached version even if there isn't a file, so we don't try to # fetch it each time. codereview.settings must always be present so do # not cache negative. gclient_utils.FileWrite(cached_file, content or '') else: content = gclient_utils.FileRead(cached_file, 'r') # Keep the content cached in memory. FILES_CACHE[filename] = content return FILES_CACHE[filename]
def GenerateDiff(files): return SVN.GenerateDiff( files, GetRepositoryRoot(), full_move=False, revision=None)