TFS: How to compare changesets between two branches - visual-studio

How can I find changesets in Branch A that were not merged to branch B programmatically.
This what Merge Window does in TFS client GUI but I need to programmatically get the list of changesets.
Say I have Microsoft.TeamFoundation.VersionControl.Client.Workspace reference.

You probably want the VersionControlServer.GetMergeCandidates() method. This gets the candidates for merging between two paths.
public MergeCandidate[] GetMergeCandidates(
string sourcePath,
string targetPath,
RecursionType recursion
)

Related

Bash reference to "parent" Git branch when creating branch off of another branch

Let's say I have a branch called parent-branch and I create a branch right off of that branch by doing
git checkout -b child-branch parent-branch
That's all fine and well of course, but what I am looking (hoping) to do, is to be able to somehow reference parent-branch from within a bash script. So for example, something like git_current_branch and git_main_branch will print the current local branch I am in, and print the master branch, respectively.
Is there a way where I can do something like git_parent_branch (or something along those lines) to have access to the parent-branch in bash and the command line. Whether that be a bash script function, or whatever other potential possibilities might work.
Is there something involving GitHub and / or any associated APIs perhaps where this is possible. I'm not overly familiar with connecting to GitHub other than just using their web interface, so anything in that respect would most likely be of big help (ideally pertaining to my issue here)!
In Git at a fundamental level, branches simply don't have parents. Well, I say simply, but it's not that simple, because we haven't defined branch, and users of Git use the word very loosely and often mean different—and contradictory, sometimes—things when they say it. So let's define branch name first, which at least has a simple, definite meaning:
A branch name is a name whose full spelling starts with refs/heads/, which—in order to exist—contains the hash ID of some existing, valid commit.
The last bit here—which is kind of redundant: an existing commit is valid, and a valid commit (whatever that means) must exist—is a concession to the fact that we can have branch names that don't exist (yet, or any more): xyzzy, for instance, is fine as a branch name, but until you create it, it's just a sort of potential branch name, floating in limbo as it were.
Because a branch name must contain a commit ID to exist, a new, empty repository—which has no commits—has no branch names either. And yet you're on the initial branch. It's in limbo, as yet nonexistent. When you make your first commit in this empty repository, then the branch name actually exists. If you like, you can re-create this special case in a non-empty repository using git checkout --orphan or git switch --orphan. (These are subtly different in how they manipulate Git's index, but both put you in this funky state of being on a branch that does not yet exist.)
This kind of special case aside, because a branch name has to contain some commit hash ID, we normally create a branch by picking some existing hash ID, just as in your example:
git checkout -b child-branch parent-branch
But what Git does with this is to resolve the name parent-branch to a commit hash ID first, then create a new branch—in this case, named child-branch—containing that hash ID. The two branch names have no parent/child relationship; we could run git checkout -b daddy kid or git checkout -b xyzzy plugh and there's no parent/child relationships here either, despite the misleading name in the daddy kid version and the neutral names in the xyzzy plugh case.
Now we come to your own question, though:
Is there a way where I can do something like git_parent_branch (or something along those lines) to have access to the parent-branch in bash and the command line.
Git contains, as a useful tool—parts of Git make use of this in various ways—a fully general string-based configuration system, where we run git config to set some arbitrary string to some arbitrary value. By convention, these strings have a hierarchical structure: user.name and user.email live within the user space; push.default is composed of push + default; and so on. Git even stores them using an INI-file-style syntax.
What this means is that although Git itself has no parent/child relationship, you can make up your own. There are a few obvious drawbacks to doing so:
Git won't maintain it for you.
You need to choose names that Git won't clobber, even in some future release (Git version 3.14 perhaps).
Nobody else will understand what the heck you're doing.
So, if you choose to do this, you're on your own—but let's note that Git does store some per-branch information in the branch.name namespace:
branch.xyzzy.remote is the remote setting for the branch named xyzzy;
branch.xyzzy.rebase is the git pull setting controlling whether the second command to use is git merge or git rebase, and depending on which second command is to be used, what flags, if any, to pass to that second command, when you're on branch xyzzy and you run git pull;
branch.xyzzy.description is the descriptive text that git format-patch will include in a cover letter, when run for branch xyzzy;
and so on. So if you were to add a branch.name.parent string value, you could store your string here. You then merely need to hope that the Git developers don't steal that name—parent—from you in the future.
Since this stuff is totally free-form, you'd just run git symbolic-ref or similar to find the current branch name, then git config --get branch.$branch.parent to get its parent setting, if it has one. If it does not have one, this must be a normal everyday parentless Git branch, rather than one of your own specially decorated branches that does have a nominal parent. To set the parent for some branch, you'd run git config branch.$branch.parent $parent, where $parent is the setting you want. (It's your decision as to whether $parent is required to be a branch name, in which case strings like xyzzy and main and plugh are fine, or whether it could be a remote-tracking name as well, in which case, you'd better use fully-qualified strings like refs/heads/xyzzy, refs/heads/main, and so forth. That will allow you to use refs/remotes/origin/main—a remote-tracking name—as a "parent".)
Is there something involving GitHub and / or any associated APIs perhaps where this is possible.
Definitely not, and this points up another weakness in the idea of using branch.$name.parent: there is no way to record this data on GitHub. It's a purely local setting. Then again, branch names are purely local: there's noting that requires that you call your development branch dev or develop, even if the development branch name in some GitHub repository you've cloned is called dev or develop.
Before I finish this off, let me add another several definitions of branch. We'll needs a few more definitions as well:
A branch tip is the commit to which a branch name points. That is, given some branch name like main that indicates some particular commit hash ID such as a123456..., the tip commit of branch main is a123456.... Checking out a branch by its name—with git checkout or git switch—and then adding a commit automatically stores the new commit's hash ID in the branch name, so that the tip commit automatically advances. The new commit's parent will be the old branch tip.
A branch (in one of its many meanings) is a set of commits that includes the tip commit of a branch (with branch here meaning name that contains a commit hash ID). Where this set of commits begins is in the mind of the user, but if left unspecified, Git generally includes every commit reachable from the tip commit.
To define reachable, see Think Like (a) Git.
A remote-tracking name is a name that exists in your Git repository but was created due to a branch name that your Git saw in some other Git repository. These names live in the refs/remotes/ namespace, which is further qualified by the remote, such as origin. For instance, refs/remotes/origin/main would be a remote-tracking name in your repository, in which your Git remembers the hash ID stored in origin's branch name main, the last time your Git got an update from their Git.
For some users, a remote branch is a branch (in the meaning of series of commits terminating at a tip commit) where the tip commit is given by a remote-tracking name. For other users—or the same user speaking at some other time—a remote branch is a branch that exists in some remote repository, such as origin. These two are easily conflated since your own origin/main tracks the other Git's main, hence the term remote-tracking name. (Git calls this a remote-tracking branch name, but the adjective remote-tracking in front of the noun name seems sufficient here.)
As you can see, the word branch is so loosely defined as to be nearly valueless. We can often reconstruct the correct definition—the one a speaker or writer had in mind—based on context, but for clarity, it's often better to use some other term.

Visual Studio branch compare giving false positives. Identical files are marked as different

I have a project in VS 2017 using default TFS version control with a main trunk and several branches. Most branches maintain the parent-child relationship, some occasionally get baseless merged.
When I compare any of the branches to each other, either parent or sibling, I get several hundred false positives, where the the compare window marks them as "Different", but when the individual files are compared, they are identical. The encoding appears to be the same as well. It basically renders the compare useless because I'm wading through hundreds of false positives.
I've tried the default VS compare, and now I've changed to Kdiff3, both give the same result.
The only other thing I can think of is the Filter string in the compare window, which is:
!bin\;!bld\;!ClientBin\;!Debug\;!obj\;!AppPackages\;!Release\;!TestResults\;!*.*~!*.appx!*.appxrecipe;!*.cache!*.cer!*.dbmdl!*.dll!*.docstates!*.docstates.suo;!*.err!*.exe!*.ilk!*.ipch!*.lastbuildstate!*.lce!*.ldf!*.lib!*.log!*.mdf!*.msscci!*.ncb!*.obj!*.opensdf!*.pch!*.pdb!*.pri!*.res!*.resources!*.sdf!*.suo!*.swp!*.temp!*.tfOrig*!*.tlog!*.tmp!*.trx!*.user!*.unsuccessfulbuild!*.v11.suo!*.vcxproj.user!*.vsix!*.vsmdi!*.vspscc!*.vssettings!*.vssscc!*.wrn!*.xap;!.metadata\
I didn't set that, I was given this project and that's how it was already setup. Since I was handed this project, the compare has always been broken in this manner.
How can I figure out why the false positives are being given, and how to fix them so that I can get a proper compare.
Images: Compare | Files are the same | Encoding is the same

Can I get the diff data for one file between two commits via the GH API?

I know how to get the commit for a file via the API, along with the SHA and all that nice stuff.
But, suppose I just want the diff of a file in 1 commit or the diff of the same file across two commits.
For instance, in this commit, say I wanted just the , :counter_sql in the activerecord/lib/active_record/associations.rb.
How do I get at that diff data via the API?
I am using Octokit.rb.
Edit 1
It seems that this is possible per this blog post, but I am just not sure how to do it with Octokit.
Edit 2
So, I am kinda figuring it out little by little.
To get the diff data between two commits, I can compare two commits like this.
So, assuming I have the two SHAs for both commits in two variables a and b, I would do something like this:
client = Octokit::Client.new(access_token: ENV["MY_ACCESS_TOKEN"])
comparison = client.compare("rails/rails", a, b, path: "activerecord/lib/active_record/associations.rb")
This issue is that this results in a diff between both commits, which includes changes to many other files and a lot of information I don't want.
All I want is the diff from this specific file across these two commits.
I haven't figured out how to do that yet.
Thoughts?
No, not possible.
You can use the Compare API to get the diff between two commits, which will include all files, not just the file you're interested in, as you observed. So you'd need to do some filtering on your end.
Another approach might be to get the contents of the file at commit X and then at commit Y, and then compute the diff between those two versions of files on your end (there's no GitHub API for diffing in general). You can use to Contents API to fetch the raw versions of files.
https://developer.github.com/v3/repos/contents/#get-contents

Git notes perfomance and alternatives

We are using git at work for a large team (>100 developers) and I am writing different scripts to provide git statistics to management.
One of the statistic that management wants to know is when commit was actually pushed to the repository. They don't really care about author date or committer date because what is matter is when the commit was pushed and therefore picked up by CI server. So I had to implement a thing like push date. Just for completeness (not to advertise myself :)) here is my blogpost describing the details http://mnaoumov.wordpress.com/2013/01/31/git-get-push-date/
Basically I use custom git notes to store details when the commit was actually pushed to the remote repository.
Let's consider a simple task: provide list of all commits between A (exclusively) and B (inclusively) and output commit hash, commit message and a push date
Well I can do something like
git log A..B --notes=push-date --format=<begin>%H<separator>%s<separator>%N<end>
And then parse things accordingly. Well this is significantly slow anyway. And also I don't like do a string parsing and I prefer strongly typed approach.
So to solve performance issues and get rid of parsing I decided to use LibGit2Sharp library.
Well if we don't touch notes it works pretty fast but as soon as I try to retrieve notes it becomes very-very slow
# PowerShell script
$pushDateNote = $commit.Notes | Where-Object -FilterScript { $_.Namespace -eq "push-date" }
$pushDate = [DateTime]::Parse($pushDateNote.Message)
For comparison if I don't include notes - results for 200 commits returned in about 2 seconds. If I include notes - time goes up to 2 minutes.
And I've checked that bottleneck here is a search note by a commit. It seems that git itself doesn't have a map between commit and note so it needs to lookup through all the notes all the time.
I've just checked we have 188921 commits in the repository, so the most likely situation will go even worse. So my solution is not scalable at all.
So my question am I doing it wrong? Maybe git is not right tool to store its own metadata efficiently? I am thinking now to move all the metadata into an external database such as MSSQL. But I'd rather keep everything in one place. Alternatively I was thinking to keep whole map between commit and its push date serialized as a note in one commit
For example to use magic hash 4b825dc642cb6eb9a060e54bf8d69288fbee4904 (Is git's semi-secret empty tree object reliable, and why is there not a symbolic name for it?)
git notes add 4b825dc642cb6eb9a060e54bf8d69288fbee4904 -m serialized-data
$serializedData = git notes show 4b825dc642cb6eb9a060e54bf8d69288fbee4904
This will help to retrieve data only once and therefore no lookup issues. But it will add additional overhead to serialize-deserialize data and this just doesn't look right to me.
Please share your thoughts
Accessing the notes from the Commit object makes libgit2 access the notes tree at each iteration of the loop. A more efficient way to do it is to:
first, load the list of commits you are interested in (you are already doing that apparently)
then load all the notes associated with the push-date namespace only once
and eventually perform a join between those two lists
note: this will add some more pressure from a memory perspective, but it should be faster.
This can be done in C# with the following code:
using (var repo = new Repository("your_repo_path"))
{
var notes = repo.Notes["push-date"];
var commits = repo.Commits.QueryBy(
new CommitFilter {Since = "1234567", Until = "89abcde"});
var pairs = from commit in commits
from note in notes
where note.TargetObjectId == commit.Id
select new {Commit = commit, Note = note};
foreach (var pair in pairs)
{
Debug.Write(pair.Commit.Sha + " : " + pair.Note);
}
}
This will output the commits which have a note associated in the push-date namespace.
note: if you are using the QueryBy syntax to retrieve the list of commits, please be aware that commit specified as Until will be excluded from the list (e.g.: as in git log A...B)
In order to also show the commits which have no notes associated in the push-date namespace, you can use the following linq query:
var pairs2 = from commit in commits
join note in notes on commit.Id equals note.TargetObjectId into gj
from subnote in gj.DefaultIfEmpty()
select new { Commit = commit, Note = subnote };
You can always consider using alternatives to 'git notes'.
See: https://www.tikalk.com/posts/2015/11/12/yet-another-way-to-implement-commit-metadata/

Target branch is empty when merging using TFS

I'm getting a Target branch empty drop-down list, on TFS:
Source branch:
$/ProjectName/TEST/ApplicationName
Target branch:
empty drop-down list, should have a "$/ProjectName/PROD/ApplicationName" option
No idea why... The branch I want to merge to, exists...
I appreciate any tips to ensure the branch shows up on the drop down list. Thank you!
Sorry... I was doing a Merge, when i needed to Branch!
Source branch: $/ProjectName/TEST/ApplicationName
Target branch name: textbox, typing value "$/ProjectName/PROD/ApplicationName"
I was able to promote from the TEST to the PROD branch now.
For the first promotion from TEST to PROD, I'm using a Branch, for the next ones, I'll use a Merge. (I'll post screenshots as soon as possible to show the dialogs.)

Resources