Learn Git Branching
learngitbranching.js.org
learngitbranching.js.org
# https://stackoverflow.com/questions/4298960/git-add-a-git-co...
git config --global alias.ac '!git add -A && git commit'
git config --global alias.acm '!git add -A && git commit -m'
git config --global alias.ll '!git log --graph --full-history --all --color --pretty=format:"%x1b[31m%h%x09%x1b[32m%d%x1b[0m%x20%s"'
git config --global alias.gst '!git status'
git config --global alias.gca '!git commit -a --amend'
git config --global alias.gp '!git push origin HEAD'
git config --global alias.bd '!git branch -d'
git config --global alias.bdd '!git branch -D'
git config --global alias.mc '!git diff --name-only --diff-filter=U'
git config --global alias.co '!git checkout'
git config --global alias.po '!git push origin'
git config --global alias.cp '!git add -A && git commit -m "Content" && git push'
Out of those, I only – but often – use `git acm` and `git co` and `git po`.
Edit: formatting (oh dear).
- `ls` for `ls-files`
- `lo` for `git log --oneline`, then add a variety of letters in multiple combinations like
- `r` for `--reverse`,
- `s` for `--stat`,
- `om` for `origin/master..`,
- `1` for `-n1`, `2` for `-n2`, `3` for `-n3` (maybe up to 5),
- `rbi` for `rebase -i`,
- `rbiom` for `rebase -i origin/master`,
- `rbc` for `rebase --continue`
- `rba` for `rebase --abort`
- `cp` for `cherry-pick`,
- `cpa` for `cherry-pick --abort`,
- `cpc` for `cherry-pick --continue`
I also have _shell_ aliases like `gits` that works out to `git status -uno`, and `gita` that lets me add tracked files. I need a `gitca` for `EDITOR=true git commit --amend`.Notice I don't have aliases for merging. I use strictly rebase and cherry-pick workflows.
I had gotten lazy about good commits, basically, `git commit -a` was in my muscle memory. Forcing myself to treat staging as a step before committing, and getting in the habit of using `git add -p` when called for, has made a meaningful difference in the quality of my commit history.
If one is disciplined about making changes in the first place, I suppose this matters less. But I'm not, and also don't want to be: if I spot something else which needs doing while I'm in flow mode, I'm not going to put it off just to keep commits nice, not when there are better options for doing that part.
that’s when the aliases are handy. The alias for fixing a typo is using all, amend and no-edit. Super useful. But obviously needs to be used with care when you only do such a fix. I’d be perfectly happy for my git oops to refuse running if there is more than one changed line to commit, for example.
Being able to define an && alias in .gitconfig would be great for being able to share snippets like these across shells and OS:es.
My most used alias is by far “git oops” for git commmit —all —-amend —-no-edit
There's no reason any of those couldn't be in a .gitconfig file.
Prepend the alias with ! it's executed via a shell. I use it to embed shell functions (which generally call git)
Eg an alias to show commits missing (useful when using cherry pick)
show-missing-commits = "!show_missing_commits() { local target=\"${2:-$(git rev-parse --abbrev-ref HEAD)}\"; printf -- 'Showing commits present in \"%s\" but not \"%s\"\n' "$1" "$target"; git log --no-merges \"$1\" ^\"${target}\"; }; show_missing_commits"What I mean is I’d like git to do it natively. But git was written assuming a posix shell and it shows up here and there sadly.
Example:
git command1 —-flag1 <some_separator> command2 —-flag2
Should be possible for git to simply interpret sequentially.Given that the entire point of POSIX is cross platform compatibility, this is one of the few times when I will say they (Linus/git) got it 100% right.
> Should be possible for git to simply interpret sequentially.
Simply? What should it do if the first command fails? Should it exit early? What if you want it to run the second only if the first returns error? What if you want to pipe the first to the second? Or use it as an argument?
Aliases have a "simple" mode where they call exactly one thing. Anything beyond that can't be simple, without also being useless.
If Microsoft can't ship a POSIX compatible shell that's on them, and if you insist on using an OS that doesn't have a POSIX compatible shell, that's on you.
I don't start Linux and ask why it doesn't run whatever people use Windows for... malware I assume?
You’d eventually reinvent a shell. But for just these 2 cases you’d just need 2 separators analogous to the & and && of the shell. But yeah it gets messy if you wanted to run a non git command in the middle.
The thing is, all those !a && b are perfectly valid in both posix and cmd. It just refuses to do the whole “if it starts with ! call the shell”. I guess all that’s missing is that it just needs to execute it. That would probably be the less complicated change. That at least doesn’t seem like a big controversial addition (maybe this is already done now it was a couple of years since I tried and hit a brick wall with aliases under cmd)
cob = !BRANCH=`git recent --no-color | fzf` && git checkout ${BRANCH}
db = !BRANCH=`git branch --no-color | fzf` && git branch -d ${BRANCH}
dbf = !BRANCH=`git branch --no-color | fzf` && git branch -D ${BRANCH}
Sure question, why have it in zshrc and load it every time a new shell starts? It’s all in the .gitconfig after the first run anyways right?
I also have a few .zshrc aliases, like `gap` which is `git add -p` or `gam` which is `git commit --amend --no-edit`.
But also some typo fixing ones that I end up doing a lot, some because I type in half a command and get distracted / check something, then start over with a partially filled in command:
alias gigit="git"
alias gitgit="git"
alias ggit="git"
alias qgit="git"
alias ght="git"
alias got="git"
alias cleear="clear"
alias gits="git s"
Another trick, in your .gitconfig, add this [alias]: git = !git
That way, if you accidentally do `git git commit` or whatever (like above) it just works. Also, some typo fixes there: commmit = commit
comit = commit git config --global alias.adog "log --all --decorate --oneline --graph"
https://stackoverflow.com/a/35075021... that's the explanation I've heard, but I agree that it's obnoxious.
I feel like if the browser requests English, odds are overwhelming that the user wants English. Assuming otherwise strikes me as someone trying to get a bit too clever with their heuristics. (And ending up in a worse place than if they hadn't bothered - location is a terrible, terrible heuristic for language.)
I think the real issue here, though, is that the language button at the bottom of this page isn't interactive until several dialogues have been clicked though. Dialogues that are likely totally incomprehensible to the person trying to change language.
This may be true now. It was less true in 90s, where this logic originated.
> The Accept-Language header defaults to the current System User Locale ID but can be overridden by user.
alias gitlg="git log --graph --all --format=format:'%C(bold blue)%h%C(reset) - %C(bold cyan)%aD%C(reset) %C(bold green)(%ar)%C(reset)%C(bold yellow)%d%C(reset)%n'' %C(white)%s%C(reset) %C(bold white)— %an%C(reset)' --abbrev-commit"
From http://stackoverflow.com/questions/1057564/pretty-git-branch...I'm not too familiar with tig, but it looks like it does a lot of what Emacs' magit does.
AFAICT, there are some things I find useful in magit that tig doesn't have:
e.g. magit allows for committing/amending/rewording with just a few strokes; afaict, tig doesn't support the equivalent out of the box.
e.g. in magit's status view, you'll get to see the diff for each change alongside the status entry. (Whereas tig limits itself to one view at a time; and viewing the diff is a different view than the status view).
1. starts instantly
2. requires no plugins or config (or an entire editor/os!)
3. available in every distro's repo
4. lets me stage and unstage lines or hunks with 1 key
I wonder if there's something about it that makes it forgettable? I don't know if I can present it any differently, maybe there's a better way of presenting it to make it more appealing.
Them: "I really want to be proficient in git." Me: "Here's the tutorial site that I used that helped me grasp concepts and be more adept." Them: "Cool thanks."
And of course they never put in the time or bother with the site ever again, repeat ad-nauseum.
A "branch" traditionally in SCM was a sequence of versions/commits. In those SCMs, the "main" (or master) branch was an identifiable sequence of versions/commits.
There isn't a "main" branch in git, there's a current "main" version/commit. If your history is fully linear, a pointer to the "main" commit allows you, via parent following, to discern a "main" branch. But if you've been using merge, for example, there isn't a unique path from the current main commit to the root.
It gets this from the branch name which by my convention always contains the ticket ref.
It would be trivial to modify this to use the full branch name instead.
.git/hooks/prepare-commit-msg
#!/usr/bin/env bash
BRANCH=$(git symbolic-ref --short HEAD 2> /dev/null)
# fail gracefully if we don't have a branch
if [ -z "$BRANCH" ]; then
exit 0
fi
# add a git trailer with the branch
git interpret-trailers --in-place --where before --if-exists addIfDifferent --trailer Branch="$BRANCH" "$1"
And if you add the following alias to your ~/.gitconfig you'll be able to see the branch names in the logs by typing git lb (for log branch): [alias]
lb = log --color --graph --pretty=format:'%Cred%h%Creset %C(bold)%(trailers:key=Branch,valueonly,separator=%x2c)%Creset - %s %Cgreen(%cr)%C(bold blue) <%an>%C(nobold yellow)%d%Creset' --abbrev-commit - original parent commit
Between this and the synopsis it
should then be possible to find all
evolutions of this commit.
- original upstream URI and branch (if
known) or upstream URI and branch
targeted for integration (prompting
if necessary)
- commit type and optional tracker ID
When a commit has these metadata already, leave it.Without changing the DAG, I can arbitrarily move "main" to point at any commit I like in git. This isn't some esoteric action - this is done constantly as part of normal git workflow.
While what people understand as a branch in traditional SCM discussions can only grow by the addition of new versions. Commits/versions belong to a specific and unique branch and once added to the version tree, cannot "move" branches.
While in git, a commit can belong to any number of (git) "branches" which makes presenting a true branch based history (if the DAG contains merges) impossible. Which is why we all end up using a workflow based on rebase instead of one based branch-and-merge.
I've observed how much confusion using the name "branch" for what is a version pointer in git causes to those starting with git. The easiest way I've found to help people is to tell them forget about the word "branch" and think in terms of the DAG and pointers to elements in the DAG.
Same in general programming, being clear on the difference between a reference and referee is vital to proper conceptualize what the operations actually do.
But I'm not sure even that's convincing as after using git for a while it becomes completely instinctual which is why it may not seem important to clearly identify a pointer to a commit and an actual sequence of commits related by the parent/child relationship.
I don’t get what is the problem with Git’s concept of branching. It is mutable, yes, and commits aren’t somehow marked with the information about what branch it was made on. I can understand that some might expect it to. But why is Git’s “branch” so strange that it doesn’t deserve to call itself that? All technical names are in the end synthetic.
> Without changing the DAG, I can arbitrarily move "main" to point at any commit I like in git. This isn't some esoteric action - this is done constantly as part of normal git workflow.
You can reset to whatever in general. But this is typically not done for the main branch. People who pull it will get an error if you rewrite it completely. Only fast-forward updates are the normal ones (when you go from an ancestor commit to a descendant).
> While in git, a commit can belong to any number of (git) "branches" which makes presenting a true branch based history (if the DAG contains merges) impossible.
A true branch history? When does this matter? You can see that main has commits and things are merged into it. That’s typical. Of course people can make a mess of that (too easy really). But usually you have a few immortal histories (we can call them histories if you want) and things eventually end up in them.
> Which is why we all end up using a workflow based on rebase instead of one based branch-and-merge.
Plenty of people use only merges. Some hate rebase. Even though they shouldn’t.
> I've observed how much confusion using the name "branch" for what is a version pointer in git causes to those starting with git. The easiest way I've found to help people is to tell them forget about the word "branch" and think in terms of the DAG and pointers to elements in the DAG.
In other words you explain to them concretely what it is. Yes? “Branch” is just a name.
Is a “bookmark” in Mercurial any more obvious?
Exactly like a branch on a tree: It's a living thing, that keeps on growing. The word "branch" refers to the whole thing, regardless of where the tip of it has grown to. Utterly intuitive.
And just like branches on trees grow at their tips, the "branch" pointer in git, where you add stuff, is the latest commit. Of course; where else should it grow?
> and commits aren’t somehow marked with the information about what branch it was made on.
Again, exactly like a branch on a tree: Pluck a leaf from it, and there is no sign on it to say which branch it came from. But you can look at any twig or leaf and see what it's attached to, all the way to the trunk, and where it forks off from the trunk is where the branch begins.
(Somewhere along the length of it, you could score the branch name into the bark of the tree... Nah, analogy getting a bit too literal there. But still, totally intuitive.)
As such pointing to the parent is more stable than pointing to when the parent.
edit: right after typing this, I clicked the title of the MacOS styled window to the left and somehow it worked, even though clicking everywhere inside it didn't.
# https://github.com/Igosuki/dotfiles/blob/master/git/.gitconf...
grep = grep -Ii
lalias = "!git config -l | grep alias | cut -c 7-"
done = "!f() { git branch | grep "$1" | cut -c 3- | grep -v done | xargs -I{} git branch -m {} done-{}; }; f"
assumed = "!git ls-files -v | grep ^h | cut -c 3-"
lasttag = describe --tags --abbrev=0
lt = describe --tags --abbrev=0
dr = "!f() { git diff "$1"^.."$1"; }; f"
lc = "!f() { git ll "$1"^.."$1"; }; f"
diffr = "!f() { git diff "$1"^.."$1"; }; f"
lb = " !f() { git branch -a | more; }; f"
cp = cherry-pick
st = status -s
cl = clone
ci = commit
br = branch
diff = diff --work-diff
dc = diff --cached
r = reset
r1 = reset HEAD^
r2 = reset HEAD^^
rh = reset --hard
rh1 = reset --hard HEAD^
rh2 = reset --hard HEAD^^
sl = stash list
sa = stash apply
ss = stash save
logtree = log --graph --oneline --decorate --all
lmine = "!f() { git log --branches --author=igosuki@gmail.com; }; f"
purgeforever = "!f() { git filter-branch --prune-empty -d /dev/shm/scratch --index-filter "git rm --cached -f --ignore-unmatch $1" --tag-name-filter cat -- --all }"
updaterefsafterpurge = "f() { git update-ref -d refs/original/refs/heads/master; git reflog expire --expire=now --all; git gc --prune=now }"
ec = config --global -e
up = !git pull --rebase --prune $@ && git submodule update --init --recursive
cob = checkout -b
cm = !git commit -m
save = !git add -A && git commit -m 'SAVEPOINT'
wip = !git add -u && git commit -m "WIP"
undo = reset HEAD~1 --mixed
amend = commit -a --amend
wipe = !git add -A && git commit -qm 'WIPE SAVEPOINT' && git reset HEAD~1 --hard
bclean = "!f() { git branch --merged ${1-master} | grep -v " ${1-master}$" | xargs -r git branch -d; }; f"
bdone = "!f() { git checkout ${1-master} && git up && git bclean ${1-master}; }; f"
pr = pull --rebase
I'd advise binding things like pr, po, cp, --rebase, --continue, to keyboard shortcuts though if you are in an IDE.I always think about using "clean up a pull request" as a fizzbuzz-ish screen in interviews. It just seems like a decent proxy for "do you care at all?".
Merging is for keeping track of a group of commits that has been taken from a feature branch and included in the mainline. Why would you clutter the feature branch with periodic main merges when you can cleanly rebase it and keep it tidy?
Merging is for bringing a group of commits from branch A into branch B. It is, quite literally, the original way to perform this operation. It's not "clutter", it's a correct picture of how the code was developed.
Ist it really? If you would See my uncleaned history, it would take you days to understand what I was even trying to do.
Your statement seems based on the assumption that someone knows how to achieve a particular thing right from the start. But that isn't always the case and there might be a lot of different approaches until the correct or best one is found.
Do you really want to review dead code that's somewhere in the commits of a feature branch?
Based on?
Considering the email workflow of the kernel I can’t really make sense of “intended way to do it”. For individual commits people send out patches. I’ve never seen an email thread where some merge topology is recorded: it’s just a list of patches. A straight line.
I’m pretty sure that people used patch queues before Git (and even now with quilt). Restacking a bunch of commits on top of the mainline is the same operation as a rebase.
I’ve certainly seen Linus get mad at another maintainer for allowing a back-merge into his history (merge main back into feature branch).
Oh, and here I though this discussion was about git. But you're talking GitHub.
https://opensource.stackexchange.com/a/380/30121
(Is this pedantic? I guess in a lot of contexts. But you talked about the original, intended way to do it. So it seems on-topic here.)
What kind of email is that?
A user (who wasn't you) called me out for talking about GitHub instead of git, and I said it was perfectly fair because the original discussion was specifically about sending pull requests, which is a term we only talk about in 2024 because GitHub made it a thing. Therefore, it's entirely fair to discuss it in the context of GitHub and not git.
Now we are five posts down into this bizarre tangent and I am unsure what point, if any, you are trying to raise here. That people now use the term pull requests when not using GitHub? I don't think I've seen it anywhere except for hosted services, but my experience is not universal.
In git syntax, the command is "git request-pull".
What's that in natural language? Perhaps... "A pull request"?
that just kinda sad. hopeless.
The issue is in submitting an MR/PR with those commits. There's an expectation among professionals that you make your work presentable before submitting it for review, although those who are new to the profession don't realize that this cleanup step is necessary (how could they? the intro courses don't teach this and they're usually struggling enough with the code).
I just wanted to throw this comment in here in case some newbie sees this and comes away thinking "oh, I can't have 'temp' or 'checkpoint' in my commit messages"
I partly blame the excessive fear mongering around rebasing, where the strict Never Rebase a Pushed Branch rule is drilled into them and they never learn why or when they can break the rule safely.
So it's an uphill fight but I just try to teach by demonstrating, frequently, exactly how they can tidy up for the merge request.
Some recommended resources for this?
If what made sense to them is temp, checkpoint, temp, temp, undo the temp, fix test, try this, that didn't work try other thing, maybe?, temp, fix test - then I don't stand a chance. Recently I reviewed one that had multiple 'rebase' commits, I have no idea.
Then make the PR small.
2. Is our 'large' the same?
3. I'd rather the PR were whatever size it needs to be to entirely do the thing it's supposed to do (and nothing else) than conform to some arbitrary size requirement.
That's when you know a PR is "large."
What's stopping you breaking such a PR into smaller chunks? Some arbitrary "it does what it's supposed to do" definition?
- Many temp commits
- Doesn’t matter: just squash
- But sometimes I want to have a few distinct (isolated commits for my PR)
- Then make the PR small
- But I have several changes
- Then make several small PRs
We keep coming back to “just make more PRs”. Which is curious, given that GitHub doesn’t even support dependent PRs. The thing you need immediately when your PRs start depending on each other (like refactor X before implementing Y, where both touch the same code).
But I can’t come to any other conclusion than that this is because of the over-focus on PR as the one and only reviewable unit. Thanks to GitHub.
I can’t really get on the small PR train. A PR has overhead and I often want to do small changes that I only happen to notice are necessary when I am in the middle of doing that one PR.
The benefit of all these small PRs is yet to be revealed.
Of course I start on work that I (not the reviewer) can work on right now.
Why do you start work you can't finish?
Whateverthefuck this is, discussion in good faith it is not.
Have you set those expectations with your team or the people you are working with? Your ‘style’ shouldn’t just show up in reviews
It all has trade-offs.I prefer seeing the dirty laundry.
And we obviously have different expectations of what should happen if the code of a certain commit is run.
We can sling around stereotypes of people failing and doing a bad job with their strategy. Then going to cry to someone (presumably you?). But I don’t think that advances the conversation.
I rather wanted to point out the following: Using a commit strategy based on git rebase is neither right nor wrong. It is not even best practice IMO. It has its own footguns.
Since the parent comment was very opinionated and cast judgement, I responded in kind.
I have been the person crying as well as the one who solved the mess, let's not kid ourselves.
Fair. :)
A commit should be atomic; it should be a complete change with code and tests all adjusted, and ideally a message explaining what and why it changes. (In practice / in my line of work this doesn't happen because it's all front-end code implementing some poorly documented user story in jira, but hey).
If I'm ever involved in hiring again I'll add git usage to my list of criteria. Along with whether they can actually touch type. I can't believe that the standards have dropped so far that basic computer skills are no longer necessary apparently.
We had various lectures on languages models, math, algorithms, networking... absolutely nothing on git (I did my classes between 2008 and 2013, things might have changed now)
Because the reality is, when it comes to Git history, no, I don't care in the slightest. I get all the information I need by:
- Reading previous PRs (the final diff)
- Checking the name on a git diff of a line
- The ticket reference
Git commits are a tool to help me write code and reverting to a "known-good" state. Once it's merged into master/main, I don't care how messy it is because 99.999999% of the time, I'll just go back to the merge commit.
But it takes surprisingly little to sell back centralization and lock-in to developers, even when working on top of a decentralized tool.
I don't care about centralisation / decentralisation in my work. What I care about is that I have the information I need to do my job.
> Unlike having to go to at least two different web applications (PRs and issue tracker)
PR descriptions can be part of your merge commit message so I don't know why you need to go to a web application if you don't want to. You can also read the full diff in git diff so I don't really see what you're upset about.
Since you don’t care about what I care about I have nothing to be upset about.
Yeah, that's why it's easy to sell centralisation to lots of people.
Like merging main back into your feature branch: just rebase. But then you often need to re-do conflict resolution. You have git-rerere but, eh, it’s not discoverable at all. But let’s say you get over that hurdle. Now the next obstacle is the “never rewrite shared history”. And if you’re in a “corporate” environment chances are that you publicize your branch when you make a PR. And it can take a few days to get approval.
Now I care. But sometimes I have doubts about whether the caring is well-founded. Exactly because sometimes people around me seem to care not one bit.