Using git is painful.
It's an important tool for every open source developer, since it's what everybody uses. If you want to contribute to the open source community, you have to learn how to use it.
Mostly every developer I know has told me they dislike it, but it is what it is, and that I should just learn it. And I have. I usually figure out how to accomplish everything I want, sometimes after failing a few times. I have read Pro Git 2 start to end, I help other developers with their issues with the tool, and have been using it for years. Regardless, I still fuck up every now and then. For what it's worth, I have used Mercurial for a few things, which I have to say is a much more user-friendly tool. I cannot say I love it, either, even if I do prefer it over git.
I believe part of the problem with both of these tools is that the wording used for the subcommands is not intuitive. Additionally, features have been implemented without any serious consideration over the year, causing the tools to act in unpredictable ways depending on which arguments you pass to which subcommands. Consistency would be key, but does not seem to exist. Some of this is alleviated by the usage of --porcelain
, although I seriously don't think this is an acceptable solution to the problem.
Just out of spite, I have decided to write up a draft for what I think is necessary in a VCS.
We are living in a world of centralised software, as much as some people might dislike it. This makes it easy to contribute code to a single location, and keeps everyone on board with the latest news. I am under the assumption that one project has a single main location, from which everyone will leech and seed to.
I am going to call my hypothetical project management software proj
, simply because it's short for project. We are doing more than just tracking changes, we are also deploying changes. All of the following commands are subcommands of proj
.
new <project_name> [directory]
: Creates a new directory in the current working directory, with the extension .proj. You can also configure proj to use another location by default, or override it with the second argument.init <project_name>
: Same as above, if directory was specified to the current directory. Useful for when you already have some work done, but didn't set up the project ahead of time.
clone <project_name>[@some_server] [directory]
: Downloads an already existing directory into ./${project_name}.proj
, or as above.By default, all three of these will change the directory into the project.
Setting up a project involves the creation of a proj/
subdirectory in the project itself, which keeps track of its own metadata, history and whatnot. It will prefer no leading dot, but will accept .proj/
if the alternative wasn't found.
Furthermore, proj will locally remember the projects you have worked on in its xdg_cache_home. You can set up proj to use a list of defaults servers, which it will attempt to use if you tried to clone without specifying the location. First one with a match wins. Note that the directory name does not need to match the project name, but it is stored in proj
's metadata, next to its own UUID which should be used when requesting a project from a server.
check-upstream
: Fetches all the changes upstream, so you can look at them.track
: Adds a file to the project tracker. This is persistent.save
: Takes a snapshot of all the tracked files as they are right now.ready
: For when you think you are done with some implementation. Opens an interactive tool which displays all the changes since the last accepted ready-state. With the tool, you will be able to select a chunk of project changes across all your files, give it a meaningful name, and repeat until there are no changes left. You can also just drop a change if it was a mistake.
Any one of these commands will refuse to work if you're on a protected branch. The release
and dev
branches are always protected. When you start up a project, you're usually put into the dev
branch (although this is configurable by upstream), meaning that you need to create a new feature branch no matter what you want to do.
start-feature <feature_name>
: Creates a new branch where you can implement changes to the project.check-feature <feature_name>[@server_name]
: Allows you to access another feature branch. This will check locally first, otherwise check upstream. If a server_name is given, this overrides any of these defaults.Each feature branch has its own unique identifier, the name is just descriptive to the user. If multiple features branches exist with the same name, you can prefix the_author-
to its name, since there is only ever a single owner of a single branch. If the same author additionally has multiple branches by the same name, they can be suffixed by a hyphen followed by an index. If no index is given, the highest number is assumed, as they grow from 0.
submit
: Turns the latest ready-state of the feature branch you're on into a patch file. If the project is configured to have a recipient location for patches, the patch file will be sent there (unless you have configured proj
to not do that).create-patch
: Same as above, but doesn't actually send it.apply-patch <path_to_file>
: Applies a patch onto the project, and stores it as a new ready state (remembering original authorship, if exists). Will refuse to work if project is dirty.We can see that there is a strong correlation between feature branches and authors. If you create a branch, you own it, meaning that you can grant anyone permission to read/write to it. Upstream can choose to display your branch so that other authors can check it out, implement it into their «highway» dev branch, or reject it entirely. Unless you have given them permission to write to your branch, they should not be able to modify it.
If upstream is happy with your feature branch, they can accept the feature, meaning that they will merge the ready-state into the dev branch.
implement <feature_name>
: Accepts a feature into the dev branch.new-release
: Updates the latest release to match the dev branch. Each release follows a semantic versioning, meaning you can easily view any specific version as if it was a tag.check-release [version]
: Changes the current branch to the release branch. If no version was given, assumes the newest, otherwise it will check for another version. Implicitly saves the state of the current feature branch.Upstream might not immediately accept your feature, and ask you to do some changes first. They give you feedback, which you can read by checking upstream.
new-feedback
: Opens an interactive session, in which the upstream maintainer can select each of your meaningful chunks, and comment on them. Furthermore, they can add a summary to the whole thing. Once they're done, they will save this into the feature.read-feedback
: Displays the comments the maintainers have sent to your feature branch.When looking at the history of the dev branch, most of them will just consist of single accepted features. If someone wants to inspect in more detail, they can see all the saved states inside that feature branch. This means that any old feature branch will remain in the project, and should not be deleted, and should be unique.
People have been using the term «forking» as a way to describe setting up a mirror or remote for a project, mostly in an effort to create their own branches which they can merge request; and I think this is completely wrong. A fork is what you do when you want to use some project's source code as a template for your own work, but you decide you don't agree with everything the maintainers do.
fork-project <original_project_name>[:the_version][@some_server] <new_project_name> [directory]
: Same as cloning a project, but gives it a new name, and a new UUID. If the version is specified, it will select it from the upstream releases, but reset the version to 1.0.0 under the new project.