def _update_callback(self): """Update the title with the current branch and directory name.""" branch = self.model.currentbranch curdir = core.decode(os.getcwd()) msg = 'Repository: %s\nBranch: %s' % (curdir, branch) self.commitdockwidget.setToolTip(msg) title = '%s: %s' % (self.model.project, branch) if self.mode == self.model.mode_amend: title += ' ** amending **' self.setWindowTitle(title) self.commitmsgeditor.set_mode(self.mode) if not self.model.amending(): # Check if there's a message file in .git/ merge_msg_path = gitcmds.merge_message_path() if merge_msg_path is None: return merge_msg_hash = utils.checksum(core.decode(merge_msg_path)) if merge_msg_hash == self.merge_message_hash: return self.merge_message_hash = merge_msg_hash cola.notifier().broadcast(signals.load_commit_message, core.decode(merge_msg_path))
def _update_callback(self): """Update the title with the current branch and directory name.""" branch = self.model.currentbranch curdir = core.decode(os.getcwd()) msg = N_('Repository: %s') % curdir msg += '\n' msg += N_('Branch: %s') % branch self.commitdockwidget.setToolTip(msg) title = '%s: %s (%s)' % (self.model.project, branch, self.model.git.worktree()) if self.mode == self.model.mode_amend: title += ' (%s)' % N_('Amending') self.setWindowTitle(title) self.commitmsgeditor.set_mode(self.mode) if not self.model.amending(): # Check if there's a message file in .git/ merge_msg_path = gitcmds.merge_message_path() if merge_msg_path is None: return merge_msg_hash = utils.checksum(core.decode(merge_msg_path)) if merge_msg_hash == self.merge_message_hash: return self.merge_message_hash = merge_msg_hash cmds.do(cmds.LoadCommitMessage, core.decode(merge_msg_path))
def _update_callback(self): """Update the title with the current branch and directory name.""" branch = self.model.currentbranch curdir = core.decode(os.getcwd()) msg = 'Repository: %s\nBranch: %s' % (curdir, branch) self.commitdockwidget.setToolTip(msg) title = '%s [%s]' % (self.model.project, branch) if self.mode in (self.model.mode_diff, self.model.mode_diff_expr): title += ' *** diff mode***' elif self.mode == self.model.mode_review: title += ' *** review mode***' elif self.mode == self.model.mode_amend: title += ' *** amending ***' self.setWindowTitle(title) if self.mode != self.model.mode_amend: self.amend_checkbox.blockSignals(True) self.amend_checkbox.setChecked(False) self.amend_checkbox.blockSignals(False) if not self.model.read_only() and self.mode != self.model.mode_amend: # Check if there's a message file in .git/ merge_msg_path = gitcmds.merge_message_path() if merge_msg_path is None: return merge_msg_hash = utils.checksum(core.decode(merge_msg_path)) if merge_msg_hash == self.merge_message_hash: return self.merge_message_hash = merge_msg_hash cola.notifier().broadcast(signals.load_commit_message, core.decode(merge_msg_path))
def read_config(self, path): """Return git config data from a path as a dictionary.""" dest = {} args = ('--null', '--file', path, '--list') config_lines = self.git.config(*args).split('\0') for line in config_lines: try: k, v = line.split('\n', 1) except ValueError: # the user has an invalid entry in their git config if not line: continue k = line v = 'true' k = core.decode(k) v = core.decode(v) if v in ('true', 'yes'): v = True elif v in ('false', 'no'): v = False else: try: v = int(v) except ValueError: pass self._map[k.lower()] = k dest[k] = v return dest
def extract_diff_header(status, deleted, encoding, with_diff_header, suppress_header, diffoutput): encode = core.encode headers = [] if diffoutput.startswith('Submodule'): if with_diff_header: return ('', diffoutput) else: return diffoutput start = False del_tag = 'deleted file mode ' output = StringIO() diff = core.decode(diffoutput, encoding=encoding).split('\n') for line in diff: if not start and '@@' == line[:2] and '@@' in line[2:]: start = True if start or (deleted and del_tag in line): output.write(encode(line) + '\n') else: if with_diff_header: headers.append(encode(line)) elif not suppress_header: output.write(encode(line) + '\n') result = core.decode(output.getvalue()) output.close() if with_diff_header: return('\n'.join(headers), result) else: return result
def process_args(args): if args.version: # Accept 'git cola --version' or 'git cola version' version.print_version() sys.exit(0) if args.git_path: # Adds git to the PATH. This is needed on Windows. path_entries = core.getenv('PATH', '').split(os.pathsep) path_entries.insert(0, os.path.dirname(core.decode(args.git_path))) compat.setenv('PATH', os.pathsep.join(path_entries)) # Bail out if --repo is not a directory repo = core.decode(args.repo) if repo.startswith('file:'): repo = repo[len('file:'):] repo = core.realpath(repo) if not core.isdir(repo): sys.stderr.write("fatal: '%s' is not a directory. " 'Consider supplying -r <path>.\n' % repo) sys.exit(-1) # We do everything relative to the repo root os.chdir(args.repo) return repo
def process_args(args): if args.version: # Accept 'git cola --version' or 'git cola version' version.print_version() sys.exit(0) # Handle session management restore_session(args) if args.git_path: # Adds git to the PATH. This is needed on Windows. path_entries = core.getenv('PATH', '').split(os.pathsep) path_entries.insert(0, os.path.dirname(core.decode(args.git_path))) compat.setenv('PATH', os.pathsep.join(path_entries)) # Bail out if --repo is not a directory repo = core.decode(args.repo) if repo.startswith('file:'): repo = repo[len('file:'):] repo = core.realpath(repo) if not core.isdir(repo): errmsg = N_('fatal: "%s" is not a directory. ' 'Please specify --repo <path>.') % repo core.stderr(errmsg) sys.exit(-1) # We do everything relative to the repo root os.chdir(args.repo) return repo
def parse(self, log_entry, sep=logsep): self.sha1 = log_entry[:40] (parents, tags, author, authdate, email, summary) = \ log_entry[41:].split(sep, 6) self.summary = summary and core.decode(summary) or '' self.author = author and core.decode(author) or '' self.authdate = authdate or '' self.email = email and core.decode(email) or '' if parents: generation = None for parent_sha1 in parents.split(' '): parent = CommitFactory.new(sha1=parent_sha1) parent.children.append(self) if generation is None: generation = parent.generation+1 self.parents.append(parent) generation = max(parent.generation+1, generation) self.generation = generation if tags: for tag in tags[2:-1].split(', '): if tag.startswith('tag: '): tag = tag[5:] # tag: refs/ elif tag.startswith('refs/remotes/'): tag = tag[13:] # refs/remotes/ elif tag.startswith('refs/heads/'): tag = tag[11:] # refs/heads/ if tag.endswith('/HEAD'): continue self.tags.add(core.decode(tag)) self.parsed = True return self
def commit_diff(sha1, git=git): commit = git.show(sha1) first_newline = commit.index("\n") if commit[first_newline + 1 :].startswith("Merge:"): return core.decode(commit) + "\n\n" + core.decode(diff_helper(commit=sha1, cached=False, suppress_header=False)) else: return core.decode(commit)
def url_for_email(email, imgsize): email_hash = core.decode(hashlib.md5(core.encode(email)).hexdigest()) # Python2.6 requires byte strings for urllib.quote() so we have # to force default_url = 'https://git-cola.github.io/images/git-64x64.jpg' encoded_url = urllib.quote(core.encode(default_url), core.encode('')) query = '?s=%d&d=%s' % (imgsize, core.decode(encoded_url)) url = 'https://gravatar.com/avatar/' + email_hash + query return url
def clone_repo(spawn=True): """ Present GUI controls for cloning a repository A new cola session is invoked when 'spawn' is True. """ url, ok = qtutils.prompt('Path or URL to clone (Env. $VARS okay)') url = os.path.expandvars(core.encode(url)) if not ok or not url: return None try: # Pick a suitable basename by parsing the URL newurl = url.replace('\\', '/') default = newurl.rsplit('/', 1)[-1] if default == '.git': # The end of the URL is /.git, so assume it's a file path default = os.path.basename(os.path.dirname(newurl)) if default.endswith('.git'): # The URL points to a bare repo default = default[:-4] if url == '.': # The URL is the current repo default = os.path.basename(os.getcwd()) if not default: raise except: cola.notifier().broadcast(signals.information, 'Error Cloning', 'Could not parse: "%s"' % url) qtutils.log(1, 'Oops, could not parse git url: "%s"' % url) return None # Prompt the user for a directory to use as the parent directory parent = QtGui.QApplication.instance().activeWindow() msg = 'Select a parent directory for the new clone' dirname = qtutils.opendir_dialog(parent, msg, cola.model().getcwd()) if not dirname: return None count = 1 dirname = core.decode(dirname) destdir = os.path.join(dirname, core.decode(default)) olddestdir = destdir if os.path.exists(destdir): # An existing path can be specified msg = ('"%s" already exists, cola will create a new directory' % destdir) cola.notifier().broadcast(signals.information, 'Directory Exists', msg) # Make sure the new destdir doesn't exist while os.path.exists(destdir): destdir = olddestdir + str(count) count += 1 cola.notifier().broadcast(signals.clone, core.decode(url), destdir, spawn=spawn) return destdir
def commit_diff(sha1): commit = git.show(sha1) first_newline = commit.index('\n') if commit[first_newline+1:].startswith('Merge:'): return (core.decode(commit) + '\n\n' + core.decode(diff_helper(commit=sha1, cached=False, suppress_header=False))) else: return core.decode(commit)
def read_git_file(f): if f is None: return None if is_git_file(f): fh = open(f) data = core.decode(core.read(fh)).rstrip() fh.close() if data.startswith("gitdir: "): return core.decode(data[len("gitdir: ") :]) return None
def execute(command, _cwd=None, _stdin=None, _raw=False, _decode=True, _encoding=None): """ Execute a command and returns its output :param command: argument list to execute. :param _cwd: working directory, defaults to the current directory. :param _decode: whether to decode output, defaults to True. :param _encoding: default encoding, defaults to None (utf-8). :param _raw: do not strip trailing whitespace. :param _stdin: optional stdin filehandle. :returns (status, out, err): exit status, stdout, stderr """ # Allow the user to have the command executed in their working dir. if not _cwd: _cwd = core.getcwd() extra = {} if sys.platform == 'win32': command = map(replace_carot, command) extra['shell'] = True # Start the process # Guard against thread-unsafe .git/index.lock files INDEX_LOCK.acquire() status, out, err = core.run_command(command, cwd=_cwd, stdin=_stdin, **extra) # Let the next thread in INDEX_LOCK.release() if _decode: out = core.decode(out, encoding=_encoding) err = core.decode(err, encoding=_encoding) if not _raw: out = out.rstrip('\n') cola_trace = GIT_COLA_TRACE if cola_trace == 'trace': msg = 'trace: ' + subprocess.list2cmdline(command) Interaction.log_status(status, msg, '') elif cola_trace == 'full': if out: core.stderr("%s -> %d: '%s' '%s'" % (' '.join(command), status, out, err)) else: core.stderr("%s -> %d" % (' '.join(command), status)) elif cola_trace: core.stderr(' '.join(command)) # Allow access to the command's status code return (status, out, err)
def clone_repo(spawn=True): """ Present GUI controls for cloning a repository A new cola session is invoked when 'spawn' is True. """ url, ok = qtutils.prompt(N_('Path or URL to clone (Env. $VARS okay)')) url = os.path.expandvars(core.encode(url)) if not ok or not url: return None try: # Pick a suitable basename by parsing the URL newurl = url.replace('\\', '/').rstrip('/') default = newurl.rsplit('/', 1)[-1] if default == '.git': # The end of the URL is /.git, so assume it's a file path default = os.path.basename(os.path.dirname(newurl)) if default.endswith('.git'): # The URL points to a bare repo default = default[:-4] if url == '.': # The URL is the current repo default = os.path.basename(os.getcwd()) if not default: raise except: Interaction.information( N_('Error Cloning'), N_('Could not parse Git URL: "%s"') % url) Interaction.log(N_('Could not parse Git URL: "%s"') % url) return None # Prompt the user for a directory to use as the parent directory msg = N_('Select a parent directory for the new clone') dirname = qtutils.opendir_dialog(msg, cola.model().getcwd()) if not dirname: return None count = 1 dirname = core.decode(dirname) destdir = os.path.join(dirname, core.decode(default)) olddestdir = destdir if os.path.exists(destdir): # An existing path can be specified msg = (N_('"%s" already exists, cola will create a new directory') % destdir) Interaction.information('Directory Exists', msg) # Make sure the new destdir doesn't exist while os.path.exists(destdir): destdir = olddestdir + str(count) count += 1 if cmds.do(cmds.Clone, core.decode(url), destdir, spawn=spawn): return destdir return None
def _stat_info(): # Try /etc/gitconfig as a fallback for the system config userconfig = os.path.expanduser(os.path.join('~', '.gitconfig')) paths = (('system', '/etc/gitconfig'), ('user', core.decode(userconfig)), ('repo', core.decode(git.instance().git_path('config')))) statinfo = [] for category, path in paths: try: statinfo.append((category, path, os.stat(path).st_mtime)) except OSError: continue return statinfo
def _stat_info(): # Try /etc/gitconfig as a fallback for the system config paths = (('system', '/etc/gitconfig'), ('user', core.decode(_USER_XDG_CONFIG)), ('user', core.decode(_USER_CONFIG)), ('repo', core.decode(git.instance().git_path('config')))) statinfo = [] for category, path in paths: try: statinfo.append((category, path, os.stat(path).st_mtime)) except OSError: continue return statinfo
def _stat_info(): # Try /etc/gitconfig as a fallback for the system config userconfig = os.path.expanduser(os.path.join("~", ".gitconfig")) paths = ( ("system", "/etc/gitconfig"), ("user", core.decode(userconfig)), ("repo", core.decode(git.instance().git_path("config"))), ) statinfo = [] for category, path in paths: try: statinfo.append((category, path, os.stat(path).st_mtime)) except OSError: continue return statinfo
def test_core_decode(self): """Test the core.decode function """ filename = helper.fixture('unicode.txt') expect = core.decode(core.encode('unicøde')) actual = core.read(filename).strip() self.assertEqual(expect, actual)
def run(self): """Create the inotify WatchManager and generate FileSysEvents""" if utils.is_win32(): self.run_win32() return # Only capture events that git cares about self._wmgr = WatchManager() if self._is_pyinotify_08x(): notifier = Notifier(self._wmgr, FileSysEvent(), timeout=self._timeout) else: notifier = Notifier(self._wmgr, FileSysEvent()) self._watch_directory(self._path) # Register files/directories known to git for filename in core.decode(self._git.ls_files()).splitlines(): filename = os.path.realpath(filename) directory = os.path.dirname(filename) self._watch_directory(directory) # self._running signals app termination. The timeout is a tradeoff # between fast notification response and waiting too long to exit. while self._running: if self._is_pyinotify_08x(): check = notifier.check_events() else: check = notifier.check_events(timeout=self._timeout) if not self._running: break if check: notifier.read_events() notifier.process_events() notifier.stop()
def config_dict(self, local=True): """parses the lines from git config --list into a dictionary""" kwargs = { 'list': True, 'global': not local, # global is a python keyword } config_lines = self.git.config(**kwargs).splitlines() newdict = {} for line in config_lines: try: k, v = line.split('=', 1) except: # value-less entry in .gitconfig continue v = core.decode(v) k = k.replace('.','_') # git -> model if v == 'true' or v == 'false': v = bool(eval(v.title())) try: v = int(eval(v)) except: pass newdict[k]=v return newdict
def extract_diff_header(status, deleted, with_diff_header, suppress_header, diffoutput): encode = core.encode headers = [] if diffoutput.startswith("Submodule"): if with_diff_header: return ("", diffoutput) else: return diffoutput start = False del_tag = "deleted file mode " output = StringIO() diff = diffoutput.split("\n") for line in diff: if not start and "@@" == line[:2] and "@@" in line[2:]: start = True if start or (deleted and del_tag in line): output.write(encode(line) + "\n") else: if with_diff_header: headers.append(line) elif not suppress_header: output.write(encode(line) + "\n") result = core.decode(output.getvalue()) output.close() if with_diff_header: return ("\n".join(headers), result) else: return result
def get_patches_from_dir(path): """Find patches in a subdirectory""" patches = [] for root, subdirs, files in core.walk(path): for name in [f for f in files if f.endswith('.patch')]: patches.append(core.decode(os.path.join(root, name))) return patches
def read(self): """Read dictionary words""" paths = [] words = self.dictwords cracklib = self.cracklib propernames = self.propernames cfg_dictionary = self.dictionary if cracklib and os.path.exists(cracklib): paths.append((cracklib, True)) elif words and os.path.exists(words): paths.append((words, True)) if propernames and os.path.exists(propernames): paths.append((propernames, False)) if cfg_dictionary and os.path.exists(cfg_dictionary): paths.append((cfg_dictionary, False)) for (path, title) in paths: try: with open(path, 'r') as f: for word in f: word = core.decode(word.rstrip()) yield word if title: yield word.title() except IOError: pass return
def all_files(): """Return the names of all files in the repository""" ls_files = git.ls_files(z=True) if ls_files: return core.decode(ls_files[:-1]).split('\0') else: return []
def prepend_path(path): # Adds git to the PATH. This is needed on Windows. path = core.decode(path) path_entries = core.getenv('PATH', '').split(os.pathsep) if path not in path_entries: path_entries.insert(0, path) compat.setenv('PATH', os.pathsep.join(path_entries))
def mimedata_from_paths(paths): """Return mimedata with a list of absolute path URLs""" abspaths = [core.abspath(path) for path in paths] urls = [QtCore.QUrl.fromLocalFile(path) for path in abspaths] unicode_urls = [core.decode(bytes(url.toEncoded().data())) for url in urls] raw_text = core.encode('\r\n'.join(unicode_urls) + '\r\n') text = QtCore.QByteArray(raw_text) mimedata = QtCore.QMimeData() mimedata.setUrls(urls) mimedata.setData('text/plain', text) mimedata.setData('UTF8_STRING', text) mimedata.setData('COMPOUND_TEXT', text) mimedata.setData('TEXT', text) mimedata.setData('STRING', text) mimedata.setData('text/plain;charset=utf-8', text) # The text/x-moz-list format is raw text encoded in utf-16. # Failure to encode prevents gnome-terminal from getting the right paths. moz_text = subprocess.list2cmdline(abspaths) moz_text = core.encode(moz_text, encoding='utf-16') mimedata.setData('text/x-moz-url', moz_text) return mimedata
def set_worktree(self, worktree): self.git.set_worktree(worktree) is_valid = self.git.is_valid() if is_valid: basename = os.path.basename(self.git.worktree()) self.project = core.decode(basename) return is_valid
def do(self): if not self.filenames: return filename = self.filenames[0] if not core.exists(filename): return editor = prefs.editor() opts = [] if self.line_number is None: opts = self.filenames else: # Single-file w/ line-numbers (likely from grep) editor_opts = { '*vim*': ['+'+self.line_number, filename], '*emacs*': ['+'+self.line_number, filename], '*textpad*': ['%s(%s,0)' % (filename, self.line_number)], '*notepad++*': ['-n'+self.line_number, filename], } opts = self.filenames for pattern, opt in editor_opts.items(): if fnmatch(editor, pattern): opts = opt break try: core.fork(utils.shell_split(editor) + opts) except Exception as e: message = (N_('Cannot exec "%s": please configure your editor') % editor) details = core.decode(e.strerror) Interaction.critical(N_('Error Editing File'), message, details)
def __init__(self): Command.__init__(self) diff = self.model.git.diff( self.model.head, unified=_config.get("diff.context", 3), no_color=True, M=True, stat=True ) self.new_diff_text = core.decode(diff) self.new_mode = self.model.mode_worktree
def _parse_diff_filenames(diff_zstr): if diff_zstr: return core.decode(diff_zstr[:-1]).split('\0') else: return []
def eval_path(path): """handles quoted paths.""" if path.startswith('"') and path.endswith('"'): return core.decode(eval(path)) else: return core.decode(path)
def set_worktree(self, path): path = core.decode(path) self._find_git_directory(path) return self.paths.worktree
def test_decode_non_utf8_string(): filename = helper.fixture('cyrillic-cp1251.txt') with open(filename, 'rb') as f: content = f.read() actual = core.decode(content) assert actual.encoding == 'iso-8859-15'
def sha1_diff(git, sha1): return core.decode(git.diff(sha1 + '~', sha1, **_common_diff_opts()))
def sha1_diff(sha1, git=git): return core.decode(git.diff(sha1 + '^!', **_common_diff_opts()))
def untracked_files(git=git): """Returns a sorted list of untracked files.""" ls_files = git.ls_files(z=True, others=True, exclude_standard=True) if ls_files: return core.decode(ls_files[:-1]).split('\0') return []
def for_each_ref_basename(refs, git=git): """Return refs starting with 'refs'.""" git_output = git.for_each_ref(refs, format='%(refname)') output = core.decode(git_output).splitlines() non_heads = filter(lambda x: not x.endswith('/HEAD'), output) return map(lambda x: x[len(refs) + 1:], non_heads)
def test_decode_None(self): """Ensure that decode(None) returns None""" expect = None actual = core.decode(None) self.assertEqual(expect, actual)
def fixture(*paths): dirname = core.decode(os.path.dirname(__file__)) return os.path.join(dirname, 'fixtures', *paths)
def test_decode_None(): """Ensure that decode(None) returns None""" expect = None actual = core.decode(None) assert expect == actual
def diff_info(sha1, git=git): log = git.log('-1', '--pretty=format:%b', sha1) decoded = core.decode(log).strip() if decoded: decoded += '\n\n' return decoded + sha1_diff(sha1)
def log(git, *args, **kwargs): return core.decode( git.log(no_color=True, no_ext_diff=True, *args, **kwargs))
def prev_commitmsg(self, *args): """Queries git for the latest commit message.""" log = self.git.log('-1', no_color=True, pretty='format:%s%n%n%b', *args) return core.decode(log)
def slurp(path, size=-1): """Slurps a filepath into a string.""" fh = open(core.encode(path)) slushy = core.read(fh, size=size) fh.close() return core.decode(slushy)
def set_worktree(self, path): self._git_dir = core.decode(path) self._git_file_path = None self._worktree = None return self.worktree()
def closeEvent(self, event): """Save state in the settings manager.""" s = settings.Settings() s.add_recent(core.decode(os.getcwd())) qtutils.save_state(self, handler=s) MainWindow.closeEvent(self, event)
def shell_usplit(s): """Returns a unicode list instead of encoded strings""" return [core.decode(arg) for arg in shell_split(s)]
def test_core_decode(): """Test the core.decode function""" filename = helper.fixture('unicode.txt') expect = core.decode(core.encode('unicøde')) actual = core.read(filename).strip() assert expect == actual
def tmp_path(*paths): """Returns a path relative to the test/tmp directory""" dirname = core.decode(os.path.dirname(__file__)) return os.path.join(dirname, 'tmp', *paths)
def closeEvent(self, event): s = settings.Settings() s.add_recent(core.decode(os.getcwd())) qtutils.save_state(self, handler=s) self.QtClass.closeEvent(self, event)