Package: gitalong
gitalongGitalong is a tool for Git repositories that seek to prevent conflicts between files when working with a team.
It uses hooks and a store to communicate local changes across all clones of a given remote.
In turns this information can be leveraged by integrations to prevent modifying files that are already changed
elsewhere.Pre-requisitesPython >=3.7Git >=2.35.1Installationpip install gitalongUsageShell# Creating a dummy project repository and its clone in current working directory.gitinit--bareproject.git
gitcloneproject.gitproject# Creating a repository that Gitalong will use to store and share local changes.# You would normally host this somewhere like GitHub so your entire team has access to it.gitinit--barestore.git# Setting up Gitalong in your project repository.# This will clone the store repository in an ignored `.gitalong` folder.# It will also start tracking a `.gitalong.json` configuration file.gitalong-Cprojectsetupstore.git--modify-permissions--tracked-extensions.jpg,.gif,.png--track-uncommitted--update-gitignore--update-hooks# Creating some files.touchproject/uncommitted.png
touchproject/local.gif
touchproject/remote.jpg
touchproject/untracked.txt# Spreading them across branches.git-Cprojectadduntracked.txt
git-Cprojectcommit-m"Add untracked.txt"git-Cprojectaddremote.jpg
git-Cprojectcommit-m"Add remote.jpg"git-Cprojectpush
git-Cprojectreset--hardHEAD^
git-Cprojectaddlocal.gif
git-Cprojectcommit-m"Add local.gif"# Updating tracked commits with current local changes.# Because we specified `track_uncommitted`. Uncommitted changes will be stored as sha-less commit.# Update permissions of all files based on track commits.# Because `modify_permssions` was passed this will update all permissions of tracked files.# Permission updates currently comes at high performance cost and is not recommended.gitalong-Cprojectupdate# Checking the status for the files we created.# Each status will show <spread> <filename> <commit> <local-branches> <remote-branches> <host> <author>.# Spread flags represent where the commit live.# It will be displayed in the following order:# <local-uncommitted><local-active-branch><local-other-branch><remote-matching-branch><remote-other-branch><clone-other-branch><clone-matching-branch><clone-uncomitted># A `+` sign means is true, while a `-` sign means false or unknown.gitalong-Cprojectstatusuncommited.jpglocal.gifremote.jpguntracked.txt# If you installed with `--modify-permissions` this will try to make the files writable.# The command will return and error code of 1 if one ore more of the files cannot be made writable.gitalong-Cprojectclaimuncommited.jpglocal.gifremote.jpguntracked.txtPythonimportosimporttempfileimportloggingfromgit.repoimportRepofromgitalongimportRepository,RepositoryNotSetup,CommitSpreaddeftest_example():"""Testing example code featured in README.md."""dirname=tempfile.mkdtemp()logging.info(dirname)# Creating a dummy project repository and its clone in temp directory.project=Repo.init(path=os.path.join(dirname,"project.git"),bare=True)project_clone=project.clone(os.path.join(dirname,"project"))try:# This will raise as we never setup that repository with Gitalong.repository=Repository(project_clone.working_dir)exceptRepositoryNotSetup:# Creating a repository that Gitalong will use to store and share local changes.# You would normally host this somewhere like GitHub so your entire team has# access to it.store=Repo.init(path=os.path.join(dirname,"store.git"),bare=True)# Setting up Gitalong in your project repository.# This will clone the registry repository in an ignored `.gitalong` folder.# It will also start tracking a `.gitalong.json` configuration file.repository=Repository.setup(store_url=store.working_dir,managed_repository=project_clone.working_dir,modify_permissions=True,tracked_extensions=[".jpg",".gif",".png"],track_uncommitted=True,update_gitignore=True,# Skipping hook update for the test.update_hooks=False,)# Creating some files.open(os.path.join(project_clone.working_dir,"uncommitted.png"),"w").close()open(os.path.join(project_clone.working_dir,"local.gif"),"w").close()open(os.path.join(project_clone.working_dir,"remote.jpg"),"w").close()open(os.path.join(project_clone.working_dir,"untracked.txt"),"w").close()# Spreading them across branches.project_clone.index.add("untracked.txt")project_clone.index.commit(message="Add untracked.txt")project_clone.index.add("remote.jpg")project_clone.index.commit(message="Add remote.jpg")project_clone.remote().push()project_clone.git.reset("--hard","HEAD^")project_clone.index.add("local.gif")project_clone.index.commit(message="Add local.gif")# Updating tracked commits with current local changes. Because we specified# `track_uncommitted`. Uncommitted changes will be stored as sha-less commit.repository.update_tracked_commits()# Update permissions of all files based on track commits. Because# `modify_permssions` was passed this will update all permissions of tracked files.# Permission updates currently comes at high performance cost and is not# recommended.locally_changed_files=repository.locally_changed_filesforfilenameinrepository.files:repository.update_file_permissions(filename,locally_changed_files)# Now we'll get the last commit for our files.# This could return a dummy commit representing uncommitted changes.uncommitted_last_commit=repository.get_file_last_commit("uncommitted.png")local_last_commit=repository.get_file_last_commit("local.gif")remote_last_commit=repository.get_file_last_commit("remote.jpg")untracked_last_commit=repository.get_file_last_commit("untracked.txt")# Getting the commit spreads.# Spread flags represent where the commit live.uncommitted_spread=repository.get_commit_spread(uncommitted_last_commit)local_spread=repository.get_commit_spread(local_last_commit)remote_spread=repository.get_commit_spread(remote_last_commit)untracked_spread=repository.get_commit_spread(untracked_last_commit)assertuncommitted_spread==CommitSpread.MINE_UNCOMMITTEDassertlocal_spread==CommitSpread.MINE_ACTIVE_BRANCHassertremote_spread==CommitSpread.REMOTE_MATCHING_BRANCHassertuntracked_spread==(CommitSpread.REMOTE_MATCHING_BRANCH|CommitSpread.MINE_ACTIVE_BRANCH)# Trying to make the files writable.assertbool(repository.make_file_writable("uncommitted.png"))isFalseassertbool(repository.make_file_writable("local.gif"))isFalseassertbool(repository.make_file_writable("remote.jpg"))isTrueassertbool(repository.make_file_writable("untracked.txt"))isFalseif__name__=="__main__":test_example()StoresAs mentioned earlier, Gitalong needs an accessible place to store and share local changes with all clones of the managed
repository.
Multiple options are offered here.Git repositoryA Git repository can be used for this purpose.
The advantage of this solution is that you won't need more infrastructure and security mechanisms than what is needed to
access your project's repository. That said, pulling and pushing the data that way is pretty slow.
This method is used in the usage examples above.JSONBin.ioJSONBin.iois a simple JSON hosting service.
You will get faster operations with this option but it may come at acostdepending on
your usage. See how to set this up below.Shellgitalong -C project setup https://api.jsonbin.io/v3/b/<BIN_ID> --store-header X-Access-Key=<ACCESS_KEY> ...Pythonrepository=Repository.setup(store_url="https://api.jsonbin.io/v3/b/<BIN_ID>",store_headers={"X-Access-Key":"<ACCESS_KEY>"},...Worth noting that<ACCESS_KEY>can be an environment variable such as$ACCESS_KEY.DevelopmentIn addition to standard pre-requisites, you will need the following:virtualenwrapper(Linux/macOS)virtualenwrapper-win(Windows)Setup your virtual environment using:mkvirtualenvgitalong
pipinstall--editable.[ci]Testingpytest--cov-report=html--cov=gitalong--profile-svgDocumentingsphinx-build./docs/source./docs/buildBuildingpythonsetup.pysdistbdist_wheelPublishingtwineupload--verbosedist/*As a bonus, build systems for installing requirements and running tests are on board the provided Sublime Text project
and can be accessed usingCtrl+Shift+B.