Then it just became a matter of mapping git CLI commands to how they manipulate the graph.
Messing with existing commits can be fine as long as you're messing with the only existing version of that commit. Commits that exist in other branches, have been shared with other people, have been pushed to remote, etc, should be left alone. Rebasing local, unpushed single commits in your one working branch is fine. Rebasing anything else will cause problems. If rebase is causing you problems, do not rebase.
Also: small commits, short branches, merge often.
Git for ages 4 and up - https://www.youtube.com/watch?v=1ffBJ4sVUb4
With the help of IDE tooling and TortoiseGit, the command line basic stuff for workflows that might not be exposed on the graphic tools.
If the repo gets corrupted, I delete it and do a fresh clone.
Life is too short to bother going deeper than that.
oh and never join any project that uses github and force squash-commits-on-pullrequest-merge. that is a sign that nobody there knows git or they don't care about code history.
edit: and yes, some rare times, rebase -i and moving things around will cause local conflicts. do not fear them. resolve and continue. it's all code you just wrote, should be easy and is part of the understanding process.
Git Command Explorer
- https://gitexplorer.com/ - https://news.ycombinator.com/item?id=28888763
Learn Git Branching
- https://learngitbranching.js.org - https://news.ycombinator.com/item?id=18504948
Git from the inside out
- https://codewords.recurse.com/issues/two/git-from-the-inside... - https://news.ycombinator.com/item?id=9272249
https://git-scm.com/book/en/v2/Git-Internals-Plumbing-and-Po...
It made me understand how simple git really it under the hood, you just need to understand how the commands operate on this simple data structure.
1. There is a similarity with Lisp. A commit is an object with a pointer to the next one (or multiple parents). These objects are garbage collected. Heads point to commits: they are like root pointers. These pointers are mutating. When a new commit is made, it's a lot like a new cons cell pushed onto a list in Lisp: (push new-commit HEAD). (Stupidly, git history isn't terminated by the equivalent of NIL; yet some git operations require a parent. There are ugly workarounds.)
2. Key insight: commits store snapshots, not deltas. Diffs are calculated.
I've slightly delved into the internals.
1. I developed a procedure for reordering commits on a branch with zero conflicts, using tree objects directly, without any rebase workflow. So that is to say, we peel back the brandch, and then create commits using the tree objects of the original commits, in whatever order we want. Because git is based on snapshots, there are no conflicts: it's not a rebase operation on cherry picks.
2. I went through an exercise once of manually creating a stash entry, by creating the right objects and editing some text files in .git
Are you using it from the command line? I feel that helps a lot. Get a good log alias[1] and get hacking. I almost exclusively use the official CLI, with the exception of git-gui for committing and rolling back patches (lines / hunks). (I find it's easier than `add -p` and that CTRL+J is a good time saver.)
1: As an example, https://github.com/aib/dotfiles/blob/master/.gitconfig#L29
When I realized what it was designed for. It was designed for one or a few people to shepherd software contributions from many people, pushing most of the work out to the masses and making the job of the shepherds easier. So it is designed kind of backwards of what you'd want for a personal project or a small team. Software integration is one of the most difficult tasks in software engineering and it requires someone who is aware of how everything operates and fits together and what the overall design is. If you use Git in a team where everyone does their own integration there is a strong likelyhood that no one actually does integration, which makes things go haywire later (when it is difficult to back out). If you don't have the resources to put someone in charge of integration (it can take a lot of time) do not use Git. Just use a good source control system and do design and integration in meetings up front (and periodically thereafter).
In some sense I think Git is inefficient since it doesn't so much guide development as reject bad code. All that rejected code is wasted time that could have been used more productively with better guidance. Yet the centralized nature of control in Git makes it look like development is being guided. If the center of control is not also doing design, integration, and guidance though it creates a lot of wasted effort and failed projects. To a large extent this is true of any method of software development, but I think using Git can make it worse because it can make it easier for sub-teams to ignore integration until it is too late. Git is not magic. Getting your code to build and run is not the same as integration.
You can build a valid git repo with simple unix shell commands, and that really helped me to understand the magic behind the git commands:
Although I do use git from the cli (or magit) primarily, this knowledge has helped me pick-up front end tools intuitively and have helped others when I've never used their preferred tool. All of this doesn't seem possible unless you've put in the effort learning what's going on behind the scenes. That is to say, git itself it neither user friendly nor intuitive it must be learned.
Knowing mercurial (hg) beforehand did slow my learning progress a bit with all the false-friends. You may find it easier to learn if you don't have to re-learn some naming.
Seriously, low-level git, while not familiar to most devs, is logical and consistent. It's the UI level that is total disaster. Don't worry, you will forget everything in a week.
And best of all it keeps a backup of your entire repository so you can undo whatever git command you just did.
That and a git tree view GUI or "git log --oneline --graph" and "git reflog" to undo stuff. Also, you're better off committing temp stuff on the current local branch with message "WIP" than using "git stash".
Rebase and force-push on your own branches only (they are mutations), pull-request when putting stuff onto other branches.
I also find the whole git tree WAY easier to read if it's fast-forward-only, and no merge commits.
Atlassian's not as popular as they used to be I guess, but I found Bitbucket presents the best view of the git tree ever. When I wanted to do anything advanced, I'd look at the Bitbucket view, do local git commands in my shell, push, and then watch Bitbucket again.
You could then graduate to forking a real repo. Again experiment fearlessly, with slightly more realistic scenarios.
In particular the golden rule of never rebasing a public branch has served me very well and I never seem to run into the "merge hell" that others complain of.
1. `git status` + gitk before and after git commands
2. guess upfront what the result of git commands will be (in terms of gitk / git status)
3. be fluent with commit, push, pull, checkout, reset, merge, and possibly rebase (I call it required but it's opinionated) - you can safely ignore the other git api in 96% of workflow
Having one commit merges is overrated
one has yet to witness the abomination that is svn, sourcesafe, or "software working on my machine" delivered by a zip file in order repent and mend ones ways
Once I read somewhere it is just a key - object, append only, DB then it seemed easy to implement the core of it, so I did. It's simpler that it sounds.