def getparent_origin(): remote_refs = git.for_each_ref('--format=%(refname)', 'refs/remotes/').split('\n') origin_branches = [ ref[len('refs/remotes/'):] for ref in remote_refs if ref.find('refs/remotes/origin') == 0 and ref[-5:] != '/HEAD' ] upstream_branches = [ ref[len('refs/remotes/'):] for ref in remote_refs if ref.find('refs/remotes/upstream') == 0 and ref[-5:] != '/HEAD' ] closest_branch = None closest_branch_diff = -1 for branch_set in [upstream_branches, origin_branches]: for branch in branch_set: short_branch = branch[branch.find('/') + 1:] if short_branch == current_branch or short_branch == closest_branch or not git.is_ancestor( branch, current_branch): continue branch_diff = len( git.log('--pretty=%H', '%s..%s' % (branch, current_branch))) if closest_branch is None or branch_diff < closest_branch_diff: closest_branch = short_branch closest_branch_diff = branch_diff if closest_branch is not None: return closest_branch return current_branch
def open_on_github(needle, selection_start=None, selection_end=None): # Identify the name of the remote parent_ref = getparent(True) parent_branch = getparent(False) remote_refs = git.for_each_ref('--format=%(refname)', 'refs/remotes/').split('\n') candidate_refs = [ remote_ref for remote_ref in remote_refs if remote_ref.find('/upstream') > -1 and remote_ref.find(parent_branch) != -1 ] if len(candidate_refs) == 0: candidate_refs = [ remote_ref for remote_ref in remote_refs if remote_ref.find('/origin') > -1 and remote_ref.find(parent_branch) != -1 ] if len(candidate_refs) == 0: print('Unable to find remote for %s' % parent_branch) return # Identify the name of the repository matching_ref = candidate_refs[0].split('/')[2] matching_remote_url = git.remote('get-url', matching_ref) matching_remote_path = matching_remote_url[matching_remote_url.find(':') + 1:-4] # Find the path to the matching file/folder, relative to the git root matching_path = get_relpath(needle) if matching_path is None: print('No matching path') return path = relpath(join(os.getcwd(), matching_path), git_root) path_type = 'blob' if isfile(path) else 'tree' # Compute the GitHub URL line_link = '' if selection_start is not None and selection_end is not None: line_link = '#L%d-L%d' % (selection_start, selection_end) elif selection_start is not None: line_link = '#L%d' % selection_start github_url = 'https://github.com/%s/%s/%s/%s%s' % ( matching_remote_path, path_type, parent_ref, path, line_link) webbrowser.open(github_url)
def commit_author_time_and_branch_ref(run, master_branch): get_refs = for_each_ref('refs/remotes/origin/**', format='%(refname:short) %(authordate:unix)') with run(get_refs) as program: for branch, t in columns(program.stdout): get_time = log(f"{master_branch}..{branch}", format='%at') with run(get_time) as inner_program: for author_time, in columns(inner_program.stdout): yield int(author_time), branch
from itertools import zip_longest from typing import Tuple, Iterable, List import matplotlib from data import columns, zip_with_tail from git import for_each_ref, show from git import cherry from process import proc_to_stdout TAGS_WITH_AUTHOR_DATE_CMD = for_each_ref( 'refs/tags/**', format='%(refname:short) %(*authordate:unix)%(authordate:unix)', sort='v:refname' ) def tags_with_author_date(run) -> Iterable[Tuple[str, int]]: proc = run(TAGS_WITH_AUTHOR_DATE_CMD) stdout = proc_to_stdout(proc) return parse_tags_with_author_date(stdout) def parse_tags_with_author_date(lines: Iterable[str]) -> Iterable[Tuple[str, int]]: return ((tag, int(date)) for tag, date in columns(lines)) def diff_of_commits_between(run, upstream: str, head: str) -> Iterable[str]: cmd = cherry(upstream, head) proc = run(cmd) stdout = proc_to_stdout(proc)
def test_references_and_objects(self, rw_dir): # [1-test_references_and_objects] import git repo = git.Repo.clone_from(self._small_repo_url(), os.path.join(rw_dir, 'repo'), branch='master') heads = repo.heads master = heads.master # lists can be accessed by name for convenience master.commit # the commit pointed to by head called master master.rename('new_name') # rename heads master.rename('master') # ![1-test_references_and_objects] # [2-test_references_and_objects] tags = repo.tags tagref = tags[0] tagref.tag # tags may have tag objects carrying additional information tagref.commit # but they always point to commits repo.delete_tag(tagref) # delete or repo.create_tag("my_tag") # create tags using the repo for convenience # ![2-test_references_and_objects] # [3-test_references_and_objects] head = repo.head # the head points to the active branch/ref master = head.reference # retrieve the reference the head points to master.commit # from here you use it as any other reference # ![3-test_references_and_objects] # [4-test_references_and_objects] log = master.log() log[0] # first (i.e. oldest) reflog entry log[-1] # last (i.e. most recent) reflog entry # ![4-test_references_and_objects] # [5-test_references_and_objects] new_branch = repo.create_head('new') # create a new one new_branch.commit = 'HEAD~10' # set branch to another commit without changing index or working trees repo.delete_head(new_branch) # delete an existing head - only works if it is not checked out # ![5-test_references_and_objects] # [6-test_references_and_objects] new_tag = repo.create_tag('my_new_tag', message='my message') # You cannot change the commit a tag points to. Tags need to be re-created self.failUnlessRaises(AttributeError, setattr, new_tag, 'commit', repo.commit('HEAD~1')) repo.delete_tag(new_tag) # ![6-test_references_and_objects] # [7-test_references_and_objects] new_branch = repo.create_head('another-branch') repo.head.reference = new_branch # ![7-test_references_and_objects] # [8-test_references_and_objects] hc = repo.head.commit hct = hc.tree hc != hct hc != repo.tags[0] hc == repo.head.reference.commit # ![8-test_references_and_objects] # [9-test_references_and_objects] assert hct.type == 'tree' # preset string type, being a class attribute assert hct.size > 0 # size in bytes assert len(hct.hexsha) == 40 assert len(hct.binsha) == 20 # ![9-test_references_and_objects] # [10-test_references_and_objects] assert hct.path == '' # root tree has no path assert hct.trees[0].path != '' # the first contained item has one though assert hct.mode == 0o40000 # trees have the mode of a linux directory assert hct.blobs[0].mode == 0o100644 # blobs have a specific mode though comparable to a standard linux fs # ![10-test_references_and_objects] # [11-test_references_and_objects] hct.blobs[0].data_stream.read() # stream object to read data from hct.blobs[0].stream_data(open(os.path.join(rw_dir, 'blob_data'), 'wb')) # write data to given stream # ![11-test_references_and_objects] # [12-test_references_and_objects] repo.commit('master') repo.commit('v0.8.1') repo.commit('HEAD~10') # ![12-test_references_and_objects] # [13-test_references_and_objects] fifty_first_commits = list(repo.iter_commits('master', max_count=50)) assert len(fifty_first_commits) == 50 # this will return commits 21-30 from the commit list as traversed backwards master ten_commits_past_twenty = list(repo.iter_commits('master', max_count=10, skip=20)) assert len(ten_commits_past_twenty) == 10 assert fifty_first_commits[20:30] == ten_commits_past_twenty # ![13-test_references_and_objects] # [14-test_references_and_objects] headcommit = repo.head.commit assert len(headcommit.hexsha) == 40 assert len(headcommit.parents) > 0 assert headcommit.tree.type == 'tree' assert headcommit.author.name == 'Sebastian Thiel' assert isinstance(headcommit.authored_date, int) assert headcommit.committer.name == 'Sebastian Thiel' assert isinstance(headcommit.committed_date, int) assert headcommit.message != '' # ![14-test_references_and_objects] # [15-test_references_and_objects] import time time.asctime(time.gmtime(headcommit.committed_date)) time.strftime("%a, %d %b %Y %H:%M", time.gmtime(headcommit.committed_date)) # ![15-test_references_and_objects] # [16-test_references_and_objects] assert headcommit.parents[0].parents[0].parents[0] == repo.commit('master^^^') # ![16-test_references_and_objects] # [17-test_references_and_objects] tree = repo.heads.master.commit.tree assert len(tree.hexsha) == 40 # ![17-test_references_and_objects] # [18-test_references_and_objects] assert len(tree.trees) > 0 # trees are subdirectories assert len(tree.blobs) > 0 # blobs are files assert len(tree.blobs) + len(tree.trees) == len(tree) # ![18-test_references_and_objects] # [19-test_references_and_objects] assert tree['smmap'] == tree / 'smmap' # access by index and by sub-path for entry in tree: # intuitive iteration of tree members print(entry) blob = tree.trees[0].blobs[0] # let's get a blob in a sub-tree assert blob.name assert len(blob.path) < len(blob.abspath) assert tree.trees[0].name + '/' + blob.name == blob.path # this is how the relative blob path is generated assert tree[blob.path] == blob # you can use paths like 'dir/file' in tree[...] # ![19-test_references_and_objects] # [20-test_references_and_objects] assert tree / 'smmap' == tree['smmap'] assert tree / blob.path == tree[blob.path] # ![20-test_references_and_objects] # [21-test_references_and_objects] # This example shows the various types of allowed ref-specs assert repo.tree() == repo.head.commit.tree past = repo.commit('HEAD~5') assert repo.tree(past) == repo.tree(past.hexsha) assert repo.tree('v0.8.1').type == 'tree' # yes, you can provide any refspec - works everywhere # ![21-test_references_and_objects] # [22-test_references_and_objects] assert len(tree) < len(list(tree.traverse())) # ![22-test_references_and_objects] # [23-test_references_and_objects] index = repo.index # The index contains all blobs in a flat list assert len(list(index.iter_blobs())) == len([o for o in repo.head.commit.tree.traverse() if o.type == 'blob']) # Access blob objects for (path, stage), entry in index.entries.items(): pass new_file_path = os.path.join(repo.working_tree_dir, 'new-file-name') open(new_file_path, 'w').close() index.add([new_file_path]) # add a new file to the index index.remove(['LICENSE']) # remove an existing one assert os.path.isfile(os.path.join(repo.working_tree_dir, 'LICENSE')) # working tree is untouched assert index.commit("my commit message").type == 'commit' # commit changed index repo.active_branch.commit = repo.commit('HEAD~1') # forget last commit from git import Actor author = Actor("An author", "*****@*****.**") committer = Actor("A committer", "*****@*****.**") # commit by commit message and author and committer index.commit("my commit message", author=author, committer=committer) # ![23-test_references_and_objects] # [24-test_references_and_objects] from git import IndexFile # loads a tree into a temporary index, which exists just in memory IndexFile.from_tree(repo, 'HEAD~1') # merge two trees three-way into memory merge_index = IndexFile.from_tree(repo, 'HEAD~10', 'HEAD', repo.merge_base('HEAD~10', 'HEAD')) # and persist it merge_index.write(os.path.join(rw_dir, 'merged_index')) # ![24-test_references_and_objects] # [25-test_references_and_objects] empty_repo = git.Repo.init(os.path.join(rw_dir, 'empty')) origin = empty_repo.create_remote('origin', repo.remotes.origin.url) assert origin.exists() assert origin == empty_repo.remotes.origin == empty_repo.remotes['origin'] origin.fetch() # assure we actually have data. fetch() returns useful information # Setup a local tracking branch of a remote branch empty_repo.create_head('master', origin.refs.master).set_tracking_branch(origin.refs.master) origin.rename('new_origin') # rename remotes # push and pull behaves similarly to `git push|pull` origin.pull() origin.push() # assert not empty_repo.delete_remote(origin).exists() # create and delete remotes # ![25-test_references_and_objects] # [26-test_references_and_objects] assert origin.url == repo.remotes.origin.url cw = origin.config_writer cw.set("pushurl", "other_url") cw.release() # Please note that in python 2, writing origin.config_writer.set(...) is totally safe. # In py3 __del__ calls can be delayed, thus not writing changes in time. # ![26-test_references_and_objects] # [27-test_references_and_objects] hcommit = repo.head.commit hcommit.diff() # diff tree against index hcommit.diff('HEAD~1') # diff tree against previous tree hcommit.diff(None) # diff tree against working tree index = repo.index index.diff() # diff index against itself yielding empty diff index.diff(None) # diff index against working copy index.diff('HEAD') # diff index against current HEAD tree # ![27-test_references_and_objects] # [28-test_references_and_objects] # Traverse added Diff objects only for diff_added in hcommit.diff('HEAD~1').iter_change_type('A'): print(diff_added) # ![28-test_references_and_objects] # [29-test_references_and_objects] # Reset our working tree 10 commits into the past past_branch = repo.create_head('past_branch', 'HEAD~10') repo.head.reference = past_branch assert not repo.head.is_detached # reset the index and working tree to match the pointed-to commit repo.head.reset(index=True, working_tree=True) # To detach your head, you have to point to a commit directy repo.head.reference = repo.commit('HEAD~5') assert repo.head.is_detached # now our head points 15 commits into the past, whereas the working tree # and index are 10 commits in the past # ![29-test_references_and_objects] # [30-test_references_and_objects] # checkout the branch using git-checkout. It will fail as the working tree appears dirty self.failUnlessRaises(git.GitCommandError, repo.heads.master.checkout) repo.heads.past_branch.checkout() # ![30-test_references_and_objects] # [31-test_references_and_objects] git = repo.git git.checkout('HEAD', b="my_new_branch") # create a new branch git.branch('another-new-one') git.branch('-D', 'another-new-one') # pass strings for full control over argument order git.for_each_ref() # '-' becomes '_' when calling it # ![31-test_references_and_objects] # [32-test_references_and_objects] ssh_executable = os.path.join(rw_dir, 'my_ssh_executable.sh') with repo.git.custom_environment(GIT_SSH=ssh_executable): # Note that we don't actually make the call here, as our test-setup doesn't permit it to # succeed. # It will in your case :) repo.remotes.origin.fetch
def test_for_each_ref_ref_glob(): assert for_each_ref('refs/heads/*') == [ "git", "for-each-ref", "refs/heads/*" ]
def test_for_each_ref_normal(): assert for_each_ref() == ["git", "for-each-ref"]
def test_for_each_ref_sort(): assert for_each_ref(sort='v:refname') == [ "git", "for-each-ref", "--sort=v:refname" ]
def test_for_each_ref_format(): assert for_each_ref(format='%(authordate:unix)') == [ "git", "for-each-ref", "--format=%(authordate:unix)" ]
import git git = repo.git git.checkout('HEAD', b="my_new_branch") # create a new branch git.branch('another-new-one') git.branch('-D', 'another-new-one') # pass strings for full control over argument order git.for_each_ref() # '-' becomes '_' when calling it
def getparent(check_tags): if git_root is None: return current_branch # Find the current branch, accounting for detached head ee_branches = ['6.2.x', '6.1.x', '6.0.x'] ee_branches = ee_branches + ['ee-%s' % branch for branch in ee_branches] de_branches = ['7.0.x', '7.0.x-private', 'ee-7.0.x'] dxp_branches = ['7.4.x', '7.3.x', '7.2.x', '7.1.x'] dxp_branches = dxp_branches + [ '%s-private' % branch for branch in dxp_branches ] if current_branch == 'master' or current_branch in ee_branches + de_branches + dxp_branches: return current_branch # Extract the full version full_version = None if isfile(join(git_root, 'release.properties')): full_version = get_file_property(join(git_root, 'release.properties'), 'lp.version') elif isfile(join(git_root, 'build.properties')) and isfile( join(git_root, 'app.server.properties')): full_version = get_file_property(join(git_root, 'build.properties'), 'lp.version') elif isfile(join(git_root, 'git-commit-portal')): with open(join(git_root, 'git-commit-portal'), 'r') as file: commit = file.readlines()[0].strip() full_version = get_git_file_property(commit, 'release.properties', 'lp.version') else: return getparent_origin() # If the short version is 6.x, then we have a shortcut short_version = '.'.join(full_version.split('.')[0:2]) base_branch = None if short_version == '6.0': base_branch = 'ee-6.0.x' elif short_version == '6.1': base_branch = 'ee-6.1.x' elif short_version == '6.2': base_branch = 'ee-6.2.x' else: master_branches = git.for_each_ref( '--format=%(refname)', 'refs/remotes/**/*master').split('\n') for branch in master_branches: if git.is_ancestor(branch, 'HEAD'): base_branch = 'master' if base_branch is None: base_branch = '%s.%s.x' % (short_version[0], short_version[2:]) # If this is master or master-private, or we've recently rebased to 7.0.x or 7.0.x-private, # then use the branch instead of the tag if not check_tags: return base_branch # Find the closest matching tag base_tag = None if base_branch in dxp_branches or base_branch in de_branches: marketplace_base_tag = git.describe( 'HEAD', '--tags', '--abbrev=0', '--match=marketplace-*-%s%s10' % (base_branch[0], base_branch[2])) branch_base_tag = git.describe( 'HEAD', '--tags', '--abbrev=0', '--match=fix-pack-*-%s%s10*' % (base_branch[0], base_branch[2])) if marketplace_base_tag is None or len(marketplace_base_tag) == 0: base_tag = branch_base_tag elif branch_base_tag is None or len(branch_base_tag) == 0: base_tag = marketplace_base_tag else: marketplace_count = int( git.rev_list('--count', '%s..HEAD' % marketplace_base_tag)) branch_count = int( git.rev_list('--count', '%s..HEAD' % branch_base_tag)) base_tag = branch_base_tag if branch_count < marketplace_count else marketplace_base_tag if base_tag is None or len(base_tag) == 0: base_tag = git.describe( 'HEAD', '--tags', '--abbrev=0', '--match=%s.%s.*-ga*' % (base_branch[0], base_branch[2])) elif base_branch in ee_branches: base_tag = git.describe('HEAD', '--tags', '--abbrev=0', '--match=fix-pack-base-6%s*' % base_branch[5]) if base_tag is None or len(base_tag) == 0: if base_branch.find('ee-') == 0: base_tag = git.describe( 'HEAD', '--tags', '--abbrev=0', '--match=%s.%s.*-ga*' % (base_branch[3], base_branch[5])) else: base_tag = git.describe( 'HEAD', '--tags', '--abbrev=0', '--match=%s.%s.*-ga*' % (base_branch[0], base_branch[2])) if base_tag is None: return base_branch if base_tag.find('marketplace-') == 0 or base_tag.find( 'fix-pack-base-') == 0 or base_tag.find( 'fix-pack-de-') == 0 or base_tag.find( 'fix-pack-dxp-') == 0 or base_tag.find('-ga') > -1: return base_tag return base_branch
def getparent(check_tags): if git_root is None: return current_branch # Find the current branch, accounting for detached head ee_branches = ['6.2.x', '6.1.x', '6.0.x'] ee_branches = ee_branches + ['ee-%s' % branch for branch in ee_branches] de_branches = ['7.0.x', '7.0.x-private', 'ee-7.0.x'] dxp_branches = ['7.3.x', '7.2.x', '7.1.x'] dxp_branches = dxp_branches + [ '%s-private' % branch for branch in dxp_branches ] if current_branch == 'master' or current_branch in ee_branches + de_branches + dxp_branches: return current_branch # Extract the full version full_version = None if isfile(join(git_root, 'release.properties')): full_version = get_file_property(join(git_root, 'release.properties'), 'lp.version') elif isfile(join(git_root, 'build.properties')) and isfile( join(git_root, 'app.server.properties')): full_version = get_file_property(join(git_root, 'build.properties'), 'lp.version') elif isfile(join(git_root, 'git-commit-portal')): with open(join(git_root, 'git-commit-portal'), 'r') as file: commit = file.readlines()[0].strip() full_version = get_git_file_property(commit, 'release.properties', 'lp.version') else: return getparent_origin() # If the short version is 6.x, then we have a shortcut short_version = '.'.join(full_version.split('.')[0:2]) base_branch = None if short_version == '6.0': base_branch = 'ee-6.0.x' elif short_version == '6.1': base_branch = 'ee-6.1.x' elif short_version == '6.2': base_branch = 'ee-6.2.x' else: # Determine the base version using build.properties if isfile(join(git_root, 'build.properties')): base_branch = get_file_property(join(git_root, 'build.properties'), 'git.working.branch.name') if base_branch is None: if isfile(join(git_root, 'git-commit-portal')): with open(join(git_root, 'git-commit-portal'), 'r') as file: commit = file.readlines()[0].strip() base_branch = get_git_file_property( commit, 'build.properties', 'git.working.branch.name') elif base_branch == 'master': if git.for_each_ref( 'refs/remotes/upstream*/%s.x' % short_version) != '': base_branch = '%s.x' % short_version if base_branch is None: base_branch = current_branch if current_branch != 'HEAD' else '7.0.x' elif base_branch == 'ee-7.0.x': base_branch = '7.0.x' elif isdir(join(git_root, 'modules/private')) and len( git.ls_files('build.properties').strip() ) == 0 and base_branch.find('-private') == -1: base_branch = '%s-private' % base_branch # If this is master or master-private, or we've recently rebased to 7.0.x or 7.0.x-private, # then use the branch instead of the tag if not check_tags: return base_branch # Find the closest matching tag base_tag = None if base_branch in dxp_branches or base_branch in de_branches: base_tag = git.describe( 'HEAD', '--tags', '--abbrev=0', '--match=fix-pack-*-%s%s10*' % (base_branch[0], base_branch[2])) if base_tag is None or len(base_tag) == 0: base_tag = git.describe( 'HEAD', '--tags', '--abbrev=0', '--match=%s.%s.*-ga*' % (base_branch[0], base_branch[2])) elif base_branch in ee_branches: base_tag = git.describe('HEAD', '--tags', '--abbrev=0', '--match=fix-pack-base-6%s*' % base_branch[5]) if base_tag is None or len(base_tag) == 0: if base_branch.find('ee-') == 0: base_tag = git.describe( 'HEAD', '--tags', '--abbrev=0', '--match=%s.%s.*-ga*' % (base_branch[3], base_branch[5])) else: base_tag = git.describe( 'HEAD', '--tags', '--abbrev=0', '--match=%s.%s.*-ga*' % (base_branch[0], base_branch[2])) if base_tag is None: return base_branch if base_tag.find('fix-pack-base-') == 0 or base_tag.find( 'fix-pack-de-') == 0 or base_tag.find( 'fix-pack-dxp-') == 0 or base_tag.find('-ga') > -1: return base_tag return base_branch
def get_branches(run): with run(for_each_ref(format='%(refname)')) as cmd: for line in cmd.stdout: yield line.strip()