Asked  6 Months ago    Answers:  5   Viewed   92 times

While helping a friend with a git problem today, I had to introduce a branch that needed to be totally separate from the master branch. The contents of this branch really had a different origin from what had been developed on the master branch, but they were going to be merged into the master branch at a later time.

I remembered from reading John Wiegley's Git from the bottom up how branches are essentially a label to a commit that follows a certain convention and how a commit is tied to a tree of files and, optionally to parent commits. We went to create a parentless commit to the existing repository using git's plumbing:

So we got rid of all files in the index ...

$ git rm -rf .

... extracted directories and files from a tarball, added those to the index ...

$ git add .

... and created a tree object ...

$ git write-tree

(git-write-tree told us the sha1sum of the created tree object.)

Then, We committed the tree, without specifying parent commits...

$ echo "Imported project foo" | git commit-tree $TREE

(git-commit-tree told us the sha1sum of the created commit object.)

... and created a new branch that points to our newly created commit.

$ git update-ref refs/heads/other-branch $COMMIT

Finally, we returned to the master branch to continue work there.

$ git checkout -f master

This seems to have worked as planned. But this is clearly not the kind of procedure I would recommend to someone who is just getting started using git, to put it mildly. Is there an easier way of creating a new branch that is entirely unrelated to everything that has happened in the repository so far?



There is a new feature (since V1.7.2) which makes this task a little more high-level than what's in any of the other answers.

git checkout now supports the --orphan option. From the man page:

git checkout [-q] [-f] [-m] --orphan <new_branch> [<start_point>]

Create a new orphan branch, named <new_branch>, started from <start_point> and switch to it. The first commit made on this new branch will have no parents and it will be the root of a new history totally disconnected from all the other branches and commits.

This doesn't do exactly what the asker wanted, because it populates the index and the working tree from <start_point> (since this is, after all, a checkout command). The only other action necessary is to remove any unwanted items from the working tree and index. Unfortunately, git reset --hard doesn't work, but git rm -rf . can be used instead (I believe this is equivalent to rm .git/index; git clean -fdx given in other answers).

In summary:

git checkout --orphan newbranch
git rm -rf .
<do work>
git add your files
git commit -m 'Initial commit'

I left <start_point> unspecified because it defaults to HEAD, and we don't really care anyway. This sequence does essentially the same thing as the command sequence in Artem's answer, just without resorting to scary plumbing commands.

Tuesday, June 1, 2021
answered 6 Months ago

You need another formatter to handle the output. Put this after your code:

NSDateFormatter *anotherDateFormatter = [[NSDateFormatter alloc] init];   
[anotherDateFormatter setDateStyle:NSDateFormatterLongStyle];
[anotherDateFormatter setTimeStyle:NSDateFormatterShortStyle];
NSLog(@"%@", [anotherDateFormatter stringFromDate:myDate]);
Saturday, June 5, 2021
answered 6 Months ago

I don't see the point of creating two branches for two teams. Work should be separated depending on its nature, not on who will work on it.

This is what I'd suggest:

  • Use feature branches: Most of your work should be on these topic branches, except tiny commits (such as typos, etc.).
    When you have a feature, bug fix, or a ticket that should be processed: create a branch feat-something, and work in there.
  • Use a dev, or release-X (where X is the release's number) branch: When a feature's branch work is done, tested, and works, rebase it into dev.
  • Never commit on master, this branch should only be rebased into by the lead developer, CTO, whatever. Rebase dev's work into master when you feel it's needed.

This is (basically) how we work on a very big project. You can work without the dev branch if your project isn't big.

Check out this famous article that shows a pretty well done workflow: A successful Git branching model.

Monday, August 30, 2021
answered 3 Months ago

When you clone an existing repository, your Git makes a new and different repository, and copies into this new repository all1 of the commits and none of the branches from the original repository. The last step of git clone is to create one branch. This branch name is yours, not theirs; it's just spelled the same as one of their names.

As you work with your clone—a different repository—you can add more and more branches to it. If you add to it all the same branches that are in the original repository, you now have all of their commits and all of their branch names (as your own branches, mind you). But until then, you just have all of their commits. That's fine, because Git isn't about branches. Git is about commits.

1The precise description is much more complicated than this, but thinking of it as "copy all of their commits and none of their branches" will get you started.

I tried to solve the issue by cloning the branch inside a new folder (typed git clone -b) and now only the branch that I cloned is appearing..

When you make a new clone—which, again, is a new repository, where you get all of the previous repository's commits but none of its branches yet—the last step of the git clone command is to run a git checkout or git switch command2 that makes one branch. The -b flag exists so that you can tell your Git which of their branch names to copy, as the last step. If you omit the -b flag, your Git asks their Git repository—the one you're cloning—which branch they recommend. But either way you get only one branch.

You don't actually need any branch names to do work in Git. You do need some kind of name, though, and branch names are the best kind of name here. That's why your Git makes one name at the end of the git clone process. Each name you make gives you one more thing to work with.

To understand what's going on, read on. If you're satisfied that your immediate question has been answered, you can stop here.

2The git switch command was first added in Git version 2.23, to split up the overly-complicated git checkout command into two separate commands, git switch and git restore. The existing git checkout remains; you can use it instead of the two new, simpler commands. The new simplified commands are in a sense safer, though: the git switch command tries to be very safe, as does the half of git checkout that it copied. The git restore command, however, is deliberately unsafe in that it will irrevocably destroy work; it copies the other half of git checkout. So if you use git checkout, you can accidentally invoke the "destroy my work" half when you think you're invoking the "safely do stuff" half.

Git is all about commits

To understand what Git is doing here and why it does this like this, start with the fact that Git itself is really all about commits. It's not about branches, though branch names help you (and Git) find commits. It's not about files, though commits contain files. It's really about the commits: everything else Git does is in service of retaining and adding commits. The commits are where things start, and are the purpose of everything else.

This means it's crucial to understand what a commit is, how you name a particular commit, and how you make a new commit. Let's start with the name.

The true name of a commit is its hash ID

You might think that a branch name would name a commit—and it sort of does, but indirectly. In fact, every commit is named by its number. Each commit has a unique number. No other commit can ever have that number: once that commit is made, that number is allocated to that commit. Because that commit takes up that number forever, the number has to be really big, and it is. Currently, each Git commit gets one out of 2160 possible numbers.3 This number gets expressed in hexadecimal as a big ugly string like e31aba42fb12bdeb0f850829e008e1e3f43af500 (this is an actual commit in a Git repository for Git itself).

This number always works: if you have this commit, that is its number, and git show e31aba42fb12bdeb0f850829e008e1e3f43af500 will show it, for instance. You can usually abbreviate the number, to as little as the first four characters if that's unambiguous, so if you have a clone of the Git repository for Git, git show e31aba42fb12bdeb0f850829e008 is almost guaranteed to work. But git show e31a doesn't because it could be short for this commit, or for commit e31a17f741..., for instance. While e31ab works today, as more commits get added, it might stop working.

These numbers look random, but aren't. In fact, each one is a cryptographic checksum of the complete contents of the commit.4 Git does a double-check when extracting any of its internal objects, including commits, that the checksum still matches, so as to detect storage failures: you tell Git to find a commit (or other object) by hash ID and it checks that the hash ID still matches. So this in turn means that no part of any commit—or any of Git's other internal objects—can ever change, either. You can make new ones, each of which gets a new and different ID, but this does not affect the existing ones, which remain in the repository.

3There are plans to redo the numbering system to use 2256 numbers, with some kind of ugly transition.

4In fact, all of Git's internal objects use this scheme. This means all saved objects are frozen for all time. This is how Git freezes and de-duplicates file contents, for instance.

What's in a commit

Now that we know one—and the deepest, as it were—way to look up a commit, by its hash ID, it's time to look at what's inside each commit. Each commit has two parts:

  • A commit holds a full snapshot of all your files. This is the main data of most commits (and usually also the bulk of the repository). Each file is stored as an internal blob object, using this same hash-name-encoding trick. This automatically de-duplicates files, so that if you make a hundred commits in a row that mostly re-use most of their files, they don't really take any extra space.

  • Each commit also holds some metadata, or information about the commit itself: who made it, when, and why, for instance. The "why" part is your log message: your own explanation to yourself and/or others later. Why is this commit better than the last one? Or at least, why it is any different, if it's not necessarily better. The goal of this particular commit might be to fix some bug, or add some new feature, or make something ready to add a new feature, or whatever. The commit itself has the updated source code, but not necessarily anything about the bug that the update is supposed to fix. This is your chance to explain that.

There's a piece of the metadata that Git generates for you, and then uses later, that you rarely see directly, and that is this: Each commit holds the raw hash ID of its immediate predecessor commit. This strings commits together, backwards, into a chain of commits that ends with the latest commit.

We can draw this. Imagine we have a repository with just three commits in it. Instead of real hash IDs, we'll use single uppercase letters to stand in for the commits. The very first commit will be A, the next will be B, and the third commit is commit C:

A <-B <-C

Since commit C is the last one, it has earlier commit B's hash ID in its metadata. We say that C points to B. By the same token, commit B points to A. Since A is the very first commit ever made, it lacks this backwards-pointing arrow: it does not point anywhere. Git calls this a (or the) root commit. It's where we get to stop working backwards.

I mentioned just a moment ago that each commit has a full snapshot of every file. But if you have Git show a commit, Git shows you what changed. How and why does Git do this?

The why is perhaps the easiest to explain. If you want to see all the files that are in the commit, you can just check out the commit. Git will copy all those files out of the commit—where, remember, they're stored in a special frozen Git format, de-duplicated (and compressed too)—to regular ordinary computer files. You probably have a bunch of file viewers that are more competent than Git could ever be: they can show you images as images, open textual documents in text editors, open PDFs with PDF viewers, and so on. But your file-viewer probably can't compare the entire snapshot with the previous entire snapshot. Git can.

Git can compare snapshot C against snapshot B pretty easily, because commit C holds commit B's hash ID. So Git can just extract both commits. Moreover, because of the way Git de-duplicates files, Git can immediately know—and not even bother extracting—the duplicated files. Git needs only to extract and compare the different files. Git will do that, and will construct a set of changes that will turn the old files into the new ones. That's what Git will show you: this set of instructions.

(Note that Git creates the set of instructions on demand. Until you ask Git to compare any two commits, all Git has are the two snapshots. You can get different sets of instructions based on options you pass to the comparison command. For instance, Git can do the difference-checking based on words, or otherwise ignore certain kinds of white-space changes. Git's abilities here are not always as good as we might like, but there are some tricks we can use. They're out of scope for this particular answer, though.)

Finding commits by branch names

We already know that if we memorize the big ugly hash IDs (or write them down), we can use those to find commits. But this is ridiculous. We have a computer. Why don't we have the computer write down the hash IDs for us?

This is what a branch name does. But it's a little sneaky. What a branch name really does is store just the last commit's hash ID. Let's draw that three-commit repository again, and add a name, main, that identifies the last commit:

A--B--C   <-- main

Here, instead of trying to remember C's hash ID, we just know that the name main does that for us. So git checkout main (pre-2.23 Git) or git switch main (2.23 and later) gets us the latest commit—currently C—no matter what hash ID it has.

We can now add a new name that also points to commit C:

A--B--C   <-- main, dev

Now we need one more thing: which of these names are we using? Right now, it doesn't matter much, because both names select commit C. But let's attach the special name HEAD to one of the two branch names, like this:

A--B--C   <-- main (HEAD), dev

If we git switch dev, we're re-attaching the special name HEAD to the name dev, like this:

A--B--C   <-- main, dev (HEAD)

Now let's make a new commit. Without worrying about how we make a new commit, let's just assume that it's all done. This new commit D will, necessarily, point back to existing commit C, because we made D from C. So that looks like this:


But D is now the latest commit, so Git has to update a name. Which name should it update? The answer is clear: it should update the one that HEAD is attached-to:

A--B--C   <-- main
        D   <-- dev (HEAD)

We now have two branch names, and the two names specify two different "latest" commits. The latest commit on main is C, and the latest commit on dev is D. Commit D points back to commit C, which points back to B, which points back to A; so all four commits are on branch dev, while three of them are on main.

If we switch back to the name main and make new a new commit there, we get:

        E   <-- main (HEAD)
        D   <-- dev

which means we now have three commits that are shared on both branches, and one commit that is only on main and one commit that is only on dev. Now we need both names to find all five commits; one name will find one commit, which will find the three shared commits, but we need the other name to find the last remaining commit.

Note that the branch names move. In fact, they move automatically, as we make new commits: whichever branch name has HEAD attached to it automatically moves to encompass the new commit. All the other branch names stay in place at that point, but because they are our branch names, we are in control. We can have our Git move those names any time we like. The only constraint is that we have to have a commit to move the name to.

Cloning creates remote-tracking names

When we clone someone else's repository, we get all their commits and none of their branches. How does this work? Well, suppose we have the above, with two actual branch names main and dev selecting commits E and D respectively. We now make a new repository where we copy all five commits, giving us:


We do actually need two names to find all the commits. But we don't need branch names. The other Git, working with the other repository, has branch names, because those are his branches that he'll move around as he makes new commits. So what our Git does is copy their names but change them. We have our Git take their branch names and create our remote-tracking names, by adding something—usually origin/—to the names.5 So we get:

        E   <-- origin/main
        D   <-- origin/dev

Git will refuse to attach the special name HEAD to one of these remote-tracking names. HEAD is only allowed to attach to a branch name. So the last step of our git clone is to use the -b option, or their recommendation, to pick one of these two names, and create a branch name from it, like this:

        E   <-- main (HEAD), origin/main
        D   <-- origin/dev

Note that our branch name selects the same commit as the remote-tracking name that our git clone made from their branch name. But we now have only one branch name, not two. If we run:

git switch dev

this uses a special feature that Git provides, that finds their origin/dev and creates our own new name dev:

        E   <-- main, origin/main
        D   <-- dev (HEAD), origin/dev

and now we have two branch names. But we didn't initially. Note that we also now have commit D checked out, rather than commit E, because git switch (or git checkout, if we use that) not only switches branches, but also selects the commit that the branch name identifies, as the commit that is to be checked-out, and therefore available to us to work with.

5Technically, a remote-tracking name is in a separate namespace. Our Git doesn't just tack origin/ in front, it replaces refs/heads/ with refs/remotes/origin/. The name origin is actually a remote and we can have as many remotes as we like in our Git repository. But this is a topic for another question.

Sunday, November 7, 2021
answered 3 Weeks ago

I'm not aware of any way to persist Type directly, but this may feel a bit better:

public class Settings
    public Type Type
        get { return Type.GetType(_TypeString); }
        set { _TypeString = value.ToString(); }

    // Backing Field
    protected virtual string _TypeString { get; set; }

Then you just need to map the protected _TypeString property (solution from here):

public static StringPropertyConfiguration Property<T>(this EntityTypeConfiguration<T> mapper, String propertyName) where T : class
        Type type = typeof(T);
        ParameterExpression arg = Expression.Parameter(type, "x");
        Expression expr = arg;

        PropertyInfo pi = type.GetProperty(propertyName,
            BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
        expr = Expression.Property(expr, pi);

        LambdaExpression lambda = Expression.Lambda(expr, arg);

        Expression<Func<T, String>> expression = (Expression<Func<T, string>>)lambda;
        return mapper.Property(expression);

Then, in your ModelBuilder:

Sunday, November 21, 2021
Marcel Dumont
answered 1 Week ago
Only authorized users can answer the question. Please sign in first, or register a free account.
Not the answer you're looking for? Browse other questions tagged :